Hibernate creates two join the same table - java

I have three tables: ScanMeta, Scan and Batch.
#Entity
#Table(name = "scan_meta_letter")
public class ScanMetaLetter {
#Id
#Column(name = "scan_meta_id")
private Long id;
#OneToOne(mappedBy = "scanMeta")
#JoinColumn(name = "scan_id")
private Scan scan;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
....
}
#Entity
#Table(name = "scan")
public class Scan {
#Id
#GeneratedValue
#Column(name = "scan_id")
private Long id;
#Column(name = "read_date")
private OffsetDateTime readDate;
#Column(name = "deletion_date")
private OffsetDateTime deletionDate;
#Column(name = "type", length = 10)
#Enumerated(EnumType.STRING)
private ScanType type;
#ManyToOne(fetch = FetchType.LAZY)
private Batch batch;
....
}
#Entity
#Table(name = "batch")
public class Batch {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "batch_status")
#Enumerated(EnumType.STRING)
private BatchStatus batchStatus;
#Column(name = "batch_type", length = 10)
#Enumerated(EnumType.STRING)
private BatchType batchType;
....
}
When I create Specification<ScanMettaLetter>, I fetch tables: Scan and Batch.
Specification<ScanMetaLetter> s = Specification.where((root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch("scan", JoinType.LEFT).fetch("batch", JoinType.LEFT);
return null;
});
If I would like the response to sort by scan.type - everything is ok. But if sort by - scan.batch.batchStatus, I get the error:
could not prepare statement; SQL [select distinct scanmetale0_.scan_meta_id as scan_met1_7_0_,
scan1_.scan_id as scan_id1_3_1_, batch2_.id as id1_0_2_, scanmetale0_.first_name as
recipien3_9_0_, scanmetale0_.last_name as recipien5_9_0_, scan1_.batch_id as
batch_i13_3_1_, scan1_.deletion_date as deletion3_3_1_, scan1_.read_date as read_dat8_3_1_,
scan1_.type as type11_3_1_, batch2_.batch_status as batch_st3_0_2_,
batch2_.batch_type as batch_ty4_0_2_ from
scan_meta_letter scanmetale0_ left outer join scan scan1_ on
scanmetale0_.scan_meta_id=scan1_.scan_id left outer join batch batch2_ on
scan1_.batch_id=batch2_.id cross join batch batch6_ where scan1_.batch_id=batch6_.id and
(scan1_.reg_code like ?) and scan1_.type=? and batch6_.batch_status=? and 1=1 order by
batch6_.batch_status asc limit ?]; nested exception is
org.hibernate.exception.SQLGrammarException: could not prepare statement
I see that two alias for batch is created -> batch2 and batch6. Why? Can I bypass this somehow?

Related

Hibernate: N+1 fix for #OneToOne

I have #OneToOne relationship with classes:
#Entity
#Table(name = "persons", schema = "persons_info")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name",
nullable = false)
#JsonProperty("first_name")
private String firstName;
#Column(name = "last_name",
nullable = false)
#JsonProperty("last_name")
private String lastName;
#Basic
#Column(name = "birth_date", nullable = false)
private Date birthDate;
#OneToOne(cascade = CascadeType.ALL,
orphanRemoval = true)
#JoinColumn(name = "address_id",
referencedColumnName = "id")
private Address address;
// setters, getters, equals...
}
Address:
#Entity
#Table(name = "addresses", schema = "persons_info")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String city;
#Column(nullable = false)
private String street;
#Column(nullable = false)
private String house;
#Column(nullable = false)
private String flat;
#JsonIgnore
#OneToOne(mappedBy = "address")
private Person person;
// setters, getters, equals...
}
I have a default JpaRepository for person:
#Repository
public interface PersonRepository
extends JpaRepository<Person, Long> {
}
And when i calling repository.findAll() i taking n+1 problem:
Hibernate:
select
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
p1_0.id,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name,
a1_0.street
from
persons_info.addresses a1_0
left join
persons_info.persons p1_0
on a1_0.id=p1_0.address_id
where
a1_0.id=?
Hibernate:
select
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
p1_0.id,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name,
a1_0.street
from
persons_info.addresses a1_0
left join
persons_info.persons p1_0
on a1_0.id=p1_0.address_id
where
a1_0.id=?
How i can fix that? (I want fetching with JOIN like this example: SELECT * FROM persons INNER JOIN address ON person.address_id = address.id)
Solved by adding #Query annotation:
#Repository
public interface PersonRepository
extends JpaRepository<Person, Long> {
#Query("""
SELECT p FROM Person p
LEFT JOIN FETCH p.address a""")
public List<Person> findAll();
}
Now that's looks like:
Hibernate:
select
p1_0.id,
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
a1_0.street,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name
from
persons_info.persons p1_0
left join
persons_info.addresses a1_0

HQL query doesn't order by other mapped entity criteria

I have the following entities in a Hibernate - Spring proyect.
Member:
#Entity
#Table(name = "member")
public class Member implements Serializable {
private static final long serialVersionUID = 1871629487715861212L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "ident_doc")
private String identDoc;
#Column(name = "join_date")
private String joinDate;
private String nickname;
#OneToOne(mappedBy = "member")
private MemberContact memberContact;
#OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberChapterLog> memberChapterLogs;
#OneToMany(mappedBy = "member")
private List<ChapterOfficers> chapterOfficers;
#OneToOne(mappedBy = "contactMember", cascade = CascadeType.ALL)
private Chapter asChapterContact;
Chapter:
#Entity
#Table(name = "chapter")
#NamedQuery(
name = "Chapter_Get_Detailed_Members_List",
query = "from MemberChapterLog where chapter.id = :paramChapter and active = true "+
"order by member.lastName asc")
public class Chapter implements Serializable {
private static final long serialVersionUID = -8387387246818721664L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String initials;
#Column(name ="chap_name")
private String chapName;
#ManyToOne
#JoinColumn(name = "category")
private ChapCategory category;
#OneToOne
#JoinColumn(name = "contact_member")
private Member contactMember;
#OneToOne(mappedBy = "chapter", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private ChapterContact chapterContact;
#OneToMany(mappedBy = "chapter")
private List<MemberChapterLog> memberChapterlogs;
#OneToMany(mappedBy = "chapter")
private List<ChapterOfficers> chapterOfficers;
MemberChapterLog:
#Entity
#Table(name = "member_chapter_log")
public class MemberChapterLog implements Serializable {
private static final long serialVersionUID = -643503606583240644L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "member_id")
private Member member;
#ManyToOne
#JoinColumn(name = "chapter_id")
private Chapter chapter;
#Column(name = "log_date")
private String logDate;
private String comment;
private boolean active;
I want to get a list of the active members of certain chapter (passed as ':paramChapter'), order by their last name. When I run the named query at chapter ("from MemberChapterLog where chapter.id = :paramChapter and active = true order by member.lastName asc") it generates the following error:
org.hibernate.HibernateException: Errors in named queries:
Chapter_Get_Detailed_Members_List failed because of: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: member near line 1, column 105 [from dev.xarlsr.cdt.entity.MemberChapterLog where chapter.id = :paramChapter and active = true order by member.lastName asc]
If I delete the order by member.lastName it works properly (without any order, byt the way).
I tried to change the mapping by changing the tables foreign keys and the ownership, but doesn't work. I tried to change the fetch type without results. What am I doing wrong?
The problem is the mapping:
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "member_id")
private Member member;
member is a keyword in JPQL/HQL and soft-keyword handling was only introduced in Hibernate 6, so you will need to change the name of the field to e.g. memberAssociation and the query condition to ... order by memberAssociation.lastName asc

Create entity using join in jpa hibernate

I'm stuck with a problem in Java, hibernate (jpa)
So, I have 2 classes: Class and Classroom, each one being entities
#Entity
#Table(name = "CLASSES")
public class Class {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Long id;
#Column(name = "TEACHER_ID")
private Long teacherId;
#Column(name = "NAME")
private String name;
#Column(name = "YEAR")
private Integer year;
#Column(name = "SECTION")
private String section;
}
#Entity
#Table(name = "CLASSROOMS")
public class Classroom {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "LOCATION")
private String location;
#Column(name = "CAPACITY")
private Integer capacity;
}
Also, I have another java class called Planner which connect these two classes (their tables - using classroom_id and class_id); I have a table for this Planner
#Entity
#Table(name = "PLANNERS")
public class Planner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "CLASSROOM_ID")
private Long classroomId;
#Column(name = "CLASS_ID")
private Long classId;
#Column(name = "STARTTIME")
private Time startTime;
#Column(name = "ENDTIME")
private Time endTime;
#Column(name = "DATA")
private Date date;
}
What I need: a new entity (or just output data) which will include all fields from PLANNERS, field NAME from CLASSES and field NAME from CLASSROOMS.
In SQL, this query is:
select M.classroom_id, M.class_id, M.starttime, M.endtime, M.data, CL.NAME AS "ROOM NAME (FROM CLASSROOMS)", C.NAME AS "COURSE NAME (FROM CLASSES)" FROM PLANNERS M INNER JOIN CLASSROOMS CL ON CL.ID = M.CLASSROOM_ID INNER JOIN CLASSES C ON C.ID = M.CLASS_ID
(inner join using classroom_id and class_id)
How can I do this on hibernate jpa? I want to get the objects (rows) returned by the above query.
I searched a lot, I find about join column, other annotations (e.g. OneToMany etc) but I didn't succeed, so I need help

Join queries with JPQL in Spring Data Jpa

I created a left join query with JPQL in spring data jpa but failed in my unit test. There are two entities in the project.
Product entity:
#Entity
#Table(name = "t_goods")
public class Product implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "name", length = 20, nullable = false)
private String name;
#Column(name = "description")
private String desc;
#Column(name = "category", length = 20, nullable = false)
private String category;
#Column(name = "price", nullable = false)
private double price;
#Column(name = "is_onSale", nullable = false)
private Integer onSale;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "brand_id")
private Brand brand;
// getter and setter
}
Brand entity:
#Entity
#Table(name = "tdb_goods_brand")
public class Brand implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "brand_name", unique = true, nullable = false)
private String name;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Product> products;
// getter and setter
}
And a third class Prod to map the query results to Object:
public class Prod implements Serializable {
private Integer id;
private String name;
private double price;
//private String brandName;
// getter and setter
}
It works fine with this query:
public interface ProductRepository extends JpaRepository<Product, Integer> {
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price) from Product p ")
Page<Prod> pageForProd(Pageable pageRequest);
}
But if I add new property brandName for Prod and refactor the query with left join, it test fails:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join com.pechen.domain.Brand b on p.brand_id = b.id")
Page<Prod> pageForProd(Pageable pageRequest);
The problem seems to be here on p.brand_id = b.id because there is not a brand_id property in Product, it's just a column name. So how can I make this work?
Update:
There turned to be some sytax errors in the JPQL query, just fix it as the following:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join p.brand b")
Page<Prod> pageForProd(Pageable pageRequest);
Besides, it's very troublesome in this way to create another class everytime to map the query results into object(I mean the Prod class). So is there a good way to work with it? Any help would be appreciated.
Instead of p.brand_id = b.id you should do p.brand.id = b.id

eclipselink SQLServerException inserting into a join table with additional column

I have a many-to-many relationship which looks like this:
The primary key is combination of three columns and I'm using eclipselink. I created these classes to be able to insert in the join-table :
#Entity
#Table(name = "definition_property")
#NamedQueries({
#NamedQuery(name = "DefinitionProperty.findAll", query = "SELECT d FROM DefinitionProperty d")})
public class DefinitionProperty extends AbstractEntity{
private static final long serialVersionUID = 1L;
#EmbeddedId
protected DefinitionPropertyPK pk;
#JoinColumn(name = "dtid", referencedColumnName = "id", insertable = false, updatable = false)
#ManyToOne(optional = false)
private DefinitionType definitionType;
#JoinColumn(name = "prid", referencedColumnName = "id", insertable = false, updatable = false)
#ManyToOne(optional = false)
private Property property;
#Column(name = "initial_value")
#Basic(optional = false)
private String initialValue;
// setters and getters
}
And PK class:
#Embeddable
public class DefinitionPropertyPK implements Serializable{
#Column(name = "dtid")
private Integer idDt;
#Column(name = "prid")
private Integer idProperty;
#Column(name = "initial_value")
private String initialValue;
//hashcode, equals, setters and getters
}
Entitiy 1:
#Entity
#Table(name = "definition_type")
public class DefinitionType extends AbstractEntity implements EntityItem<Integer> {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer idDT;
#Size(max = 45)
#Column(name = "name")
private String dtName;
#Size(max = 45)
#Column(name = "description")
private String description;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "definitionType")
private List<DefinitionProperty> definitionProperties = new ArrayList<DefinitionProperty>();
}
Entity 2:
#Entity
#Table(name = "property")
public class Property extends AbstractEntity implements EntityItem<Integer>{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Basic(optional = false )
#Column(name = "id")
private Integer idProperty;
#Column(name = "name")
#Size(max = 45)
private String propertyName;
#Enumerated(EnumType.STRING)
#Column(name = "type")
private Type fieldType;
#Column(name = "init_value")
#Size(max = 45)
private String initialValue;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "property")
private List<DefinitionProperty> definitionPeoperties= new ArrayList<DefinitionProperty>();
}
Exception : I get this exception when trying to persist a new DefinitionType:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: The column name 'initial_value' is specified more than once in the SET clause. A column cannot be assigned more than one value in the same SET clause. Modify the SET clause to make sure that a column is updated only once. If the SET clause updates columns of a view, then the column name 'initial_value' may appear twice in the view definition.
Error Code: 264
Call: INSERT INTO definition_property (initial_value, initial_value, dtid, prid) VALUES (?, ?, ?, ?)
bind => [4 parameters bound]
Question : Why there are two initial_values in the set clause and where am I wrong in my code?
Ref: How do you Set Up a Many-to-Many Relationship with Junction Table using JPA/EclipseLink
Ref: JPA simple many-to-many with eclipselink
Ref: https://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table-with-extra-column-using-jpa/

Categories