Hibernate generates invalid internal connection query for MySQL
I am developing my first project with JPA, with MySQL as my database and Hibernate 4.3.8 as my JPA provider in a Spring 4 web project.
In my Spring configuration, I set up the database and dialect:
HibernateJpaVendorAdapter hjpaVA = new HibernateJpaVendorAdapter();
hjpaVA.setDatabase(Database.MYSQL);
hjpaVA.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
I am trying to get this request which is causing me problems:
TypedQuery<KundeDTO> query = entityManager.createQuery("select new zdb.dto.KundeDTO(k.id, k.firma.firmenname, k.regnr, k.kategorie)
from Kunde k where k.id = :id", KundeDTO.class);
This is the SQL that Hibernate generates:
select kunde0_.`id` as col_0_0_, firma1_.`firmenname` as col_1_0_, kunde0_.`regnr` as col_2_0_, kunde0_.id_kategorie as col_3_0_
from `zdb_e`.`Kunde` kunde0_, `zdb_e`.`Firma` firma1_
inner join `zdb_e`.`Kategorie` kategorie2_ on kunde0_.id_kategorie=kategorie2_.`id`
where kunde0_.id_firma=firma1_.`id` and kunde0_.`id`=1;
Note that there are no parentheses in the inner join!
Running this statement results in the following error:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'kunde0_.id_kategorie' in 'on clause'
The reason for the exception is detailed here: mysql-unknown-column-in-on-clause
When I add parentheses to the where and inner join clauses and run the statement directly against the database, it works:
select kunde0_.`id` as col_0_0_, firma1_.`firmenname` as col_1_0_, kunde0_.`regnr` as col_2_0_, kunde0_.id_kategorie as col_3_0_
from (`zdb_e`.`Kunde` kunde0_, `zdb_e`.`Firma` firma1_ )
inner join `zdb_e`.`Kategorie` kategorie2_ on
( kunde0_.id_kategorie=kategorie2_.`id` )
where kunde0_.id_firma=firma1_.`id` and kunde0_.`id`=1;
So how can I convince Hibernate to generate such a request?
update: here are the entities
Kunda
@Entity
public class Kunde implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Integer id;
private Integer regnr;
@OneToOne(optional=false)
@JoinColumn(name="id_firma", nullable = false)
private Firma firma;
@OneToOne(optional=false)
@JoinColumn(name="id_kategorie", nullable = false)
private Kategorie kategorie;
@OneToOne(optional=false)
@JoinColumn(name="id_lieferregion", nullable = false)
private Lieferregion lieferregion;
// getters and setters....
}
Firm
@Entity
@Table(name = "Firma")
public class Firma implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Integer id;
@Column(name="firmenname")
private String firmenname;
@Column(name="uid")
private String uid;
@OneToOne(optional=false)
@JoinColumn(name="id_anschrift", nullable = false)
private Anschrift anschrift;
@OneToMany(mappedBy="id_firma", fetch=FetchType.EAGER)
private List<Person> personen;
public Firma() {
personen = new ArrayList<Person>();
}
// getters and setters....
}
Kategorie
@Entity
public class Kategorie implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Integer id;
private Integer nummer;
private String bezeichnung;
public Kategorie() {
}
public Kategorie(int kategorieId, int kategorieNummer, String kategorieBezeichnung) {
this.id = kategorieId;
this.nummer = kategorieNummer;
this.bezeichnung = kategorieBezeichnung;
}
// getters and setters....
}
DB schemas
CREATE TABLE kategorie
(id INTEGER NOT NULL AUTO_INCREMENT,
nummer INTEGER NOT NULL,
bezeichnung VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
UNIQUE (nummer, bezeichnung)
);
CREATE TABLE firma
(ID INTEGER NOT NULL AUTO_INCREMENT,
firmenname VARCHAR(50) NOT NULL,
uid VARCHAR(20) NOT NULL,
url VARCHAR(100) NOT NULL,
id_anschrift INTEGER NOT NULL,
id_logo INTEGER,
PRIMARY KEY (id),
UNIQUE (uid),
UNIQUE (firmenname),
UNIQUE (id_anschrift),
CONSTRAINT firma_fk1 FOREIGN KEY (id_anschrift) REFERENCES ANSCHRIFT (ID),
CONSTRAINT firma_fk2 FOREIGN KEY (id_logo) REFERENCES logo (ID));
CREATE TABLE kunde
(id INTEGER NOT NULL AUTO_INCREMENT,
regnr INTEGER NOT NULL,
id_kategorie INTEGER NOT NULL,
id_firma INTEGER NOT NULL,
id_benutzer INTEGER,
id_lieferregion INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (regnr, id_kategorie),
UNIQUE (id_firma),
UNIQUE (id_benutzer),
CONSTRAINT kunde_fk1 FOREIGN KEY (id_firma) REFERENCES firma (id),
CONSTRAINT kunde_fk2 FOREIGN KEY (id_benutzer) REFERENCES benutzer (id),
CONSTRAINT kunde_fk3 FOREIGN KEY (id_kategorie) REFERENCES kategorie (id),
CONSTRAINT kunde_fk4 FOREIGN KEY (id_lieferregion) REFERENCES lieferregion (id)
);
with further testing
The problem is missing parentheses in the from clause.
Going directly to db:
select k.id, k.regnr, f.firmenname from (Kunde k, Firma f) JOIN kategorie kat on k.id_kategorie = kat.id where k.id = 1 and k.id_firma = f.id;
works!
select k.id, k.regnr, f.firmenname from Kunde k, Firma f JOIN kategorie kat on k.id_kategorie = kat.id where k.id = 1 and k.id_firma = f.id;
Doesn't work: Unknown column "k.id_kategorie" in section "on"
Why do I even need parentheses in the from clause?
And how can I get Hibernate to insert them?
source to share
I didn't get it to work with JPQL, so I did it as jgr in the comments and wrote my own custom SQL query:
String sql = "select k.id, f.firmenname, k.regnr, kat.id, kat.nummer, kat.bezeichnung from (zdb_e.Kunde k, zdb_e.Firma f) JOIN zdb_e.kategorie kat on k.id_kategorie = kat.id where k.id = :id and k.id_firma = f.id;";
Query query = entityManager.createNativeQuery(sql, schemaName), "KundenListeRow");
query.setParameter("id", id);
return (KundeDTO) query.getSingleResult();
For mapping to my Pojo KundeDTO, I added @SqlResultSetMapping to the Kunde object:
@Entity
@SqlResultSetMapping(name = "KundenListeRow",
classes = {
@ConstructorResult(targetClass = KundeDTO.class,
columns = {
@ColumnResult(name = "id"),
@ColumnResult(name = "firmenname"),
@ColumnResult(name = "regnr"),
@ColumnResult(name = "kat.id"),
@ColumnResult(name = "kat.nummer"),
@ColumnResult(name = "kat.bezeichnung")
}
)
})
public class Kunde implements Serializable {
....
}
And the corresponding constructor in the DTO:
public class KundeDTO {
int id;
String firmenname;
Kategorie k;
int regnr;
public KundeDTO(int id, String firmenname, int regnr, int kategorieId, int kategorieNummer, String kategorieBezeichnung) {
this.id = id;
this.firmenname = firmenname;
this.regnr = regnr;
this.k = new Kategorie(kategorieId, kategorieNummer, kategorieBezeichnung);
}
public KundeDTO() {
}
}
As I said, it works. But this is not ideal. With JPQL, I could use TypedQuery and not deal with resultSetMapping.
Of course, I still don't know why my JPQL isn't working. :-)
source to share