Spring data jpa operation between table views - java

in my spring data application i have two TABLE VIEW mapped:
the first view
#Entity
#Immutable
#Table(name="VD_CONT")
#NamedQuery(name="VdContr.findAll", query="SELECT d FROM VdContr d")
public class VdContr {
#Id
#Column(name="CONTR_ID")
private Long id;
#Column(name="CF")
private String cf;
#OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="vdcontr")
private List<VdArr> vdArr;
}
and the second view
#Entity
#Immutable
#Table(name="VD_ARR")
#NamedQuery(name="VdArr.findAll", query="SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name="ARR_ID")
private Long id;
#Column(name="FK_CONTR_ID")
private Long fkContrId;
#ManyToOne(fetch=FetchType.LAZY)
public VdContr vdcontr;
}
If i put a relationship "OneToMany" and "ManyToOne" (1, first view : many, second view), i receive errors.
My question is: is it possibile create a relationship between two table view?

you need to add a #JoinColumn to VdContr.
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "vdcontr_id", nullable = false)

In general, views are mapped in the same way as tables.
By looking at your classes, the problem is that Hibernate cannot find the correct join column. You need to specify it.
Also, in your VdArr you should delete the fkContrId, because hibernate will need to use this column to map the VdContr relationship.
By looking at your code, the join column is FK_CONTR_ID, so you need to specify it by using #JoinColumn.
#Entity
#Immutable
#Table(name = "VD_ARR")
#NamedQuery(name = "VdArr.findAll", query = "SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name = "ARR_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_CONTR_ID")
public VdContr vdcontr;
}

Related

Eager fetch performs left join in hibernate but fires seperate sql queries in springboot/JPA

I see a lot of posts where Eager fetch performs left join of child table parent table in hibernate. But when I use springboot , hibernate fires seperate sql queries - means one select query for parent table and one select query for child table. Why is there a difference? Has there been any upgrades in springboot or is it something I am doing wrong ?
Below are the entities I am using:
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#Id
#Column(name = "ORDER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int orderId;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private Customer customer;
}
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#Id
#Column(name = "CUSTOMER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long customerId;
#Column(name = "CUSTOMER_NAME")
private String customer_name;
#Column(name = "CUSTOMER_address_id")
private int customer_address_id;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Order> order;
}
Controller:
#RequestMapping(value="/getByCustid/{id}", method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_VALUE)
public Customer getByCustid (#PathVariable Long id) {
Customer s1 = customerRepository.findByCustomerId(id);
return s1;
}
Repository:
public interface CustomerRepository extends JpaRepository<Customer,Long> {
public Customer findByCustomerId(Long customerId);
}
Below are the queries that are getting executed:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_,
customer0_.CUSTOMER_address_id as CUSTOMER2_0_,
customer0_.CUSTOMER_NAME as CUSTOMER3_0_,
customer0_.DAT_SRC_ID as DAT_SRC_4_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
select
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_1_,
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_1_,
order0_.DAT_SRC_ID as DAT_SRC_2_5_1_
from
Ordertable order0_
where
order0_.ORDER_CUSTOMER_ID=?
EDIT : is this related to #Fetch(FetchMode.JOIN) -- Link:JPA eager fetch does not join
Hibernate: Multiple select queries made by Hibernate for Fetch mode Eager
To check whether FetchMode.JOIN works, I have added FetchMode.JOIN in entities as shown below but still no success with Join Query:
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private List<Order> order;
}
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#JsonIgnore
#ManyToOne()
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private Customer customer;
}
The findByCustomerId will actually generate a query based on that method instead of using em.find. It will create something along the lines of SELECT c FROM Customer c WHERE c.customerId=:customerId. afterwards it will notice the fetch strategy and obtain the needed references. This is also explained here. The query will do exactlly what you instruct it to do.
If you want to eagerly load the reference you would need to write the query yourself along the lines of SELECT c FROM Customer c JOIN FETCH c.orders o WHERE c.customerId=:customerId, this will automatically retrieve the orders.
However the customerId is actually the primary key or identitifier for your entity and thus you should actually be using the findById or findOne method (depending on your Spring Data JPA version). This will use the EntityManager.find which should take the mapping information into account and create the appropriate query.

Spring Jpa Data Repository save (update) with LinkedEntity for ManyToMany relationship

There are 2 entities (lets say Rule and Label) with many-to-many relationship using linked entity
as per hibernate reference documentation
Rule enity:
#Entity
#Table(name = "rule")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public class Rule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NaturalId
#NotBlank
#Column(unique = true)
private String name;
#Lob
#Column(columnDefinition = "TEXT")
private String content;
#OneToMany(mappedBy = "rule", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> labels = new ArrayList<>();
...
Label entity:
#Entity
#Table(name = "label")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Label implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String name;
#OneToMany(mappedBy = "label", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> rules = new ArrayList<>();
...
Link entity:
#Entity
public class RuleLabel implements Serializable {
#Id
#ManyToOne
private Rule rule;
#Id
#ManyToOne
private Label label;
...
Repositories:
#Repository
public interface LabelRepository extends JpaRepository<Label, Long>
...
#Repository
public interface RuleRepository extends JpaRepository<Rule, Long>
...
Creating new entity via RuleRepository.save(Rule) works fine, but when I'm trying to update existing entity (the same method RuleRepository.save(Rule), but entity to be saved contains id field) it leads to infinite loop of Hibernate: select... queries:
Hibernate: select rule0_.id as id1_7_1_, rule0_.is_active as is_activ2_7_1_, rule0_.content as content3_7_1_, rule0_.is_deleted as is_delet4_7_1_, rule0_.import_section as import_s5_7_1_, rule0_.name as name6_7_1_, rule0_.rule_configuration as rule_con7_7_1_, labels1_.rule_id as rule_id1_8_3_, labels1_.label_id as label_id2_8_3_, labels1_.rule_id as rule_id1_8_0_, labels1_.label_id as label_id2_8_0_ from rule rule0_ left outer join rule_label labels1_ on rule0_.id=labels1_.rule_id where rule0_.id=?
and StackOverflowError as a result
java.lang.StackOverflowError: null
at com.mysql.jdbc.ServerPreparedStatement.getInstance(ServerPreparedStatement.java:332)
...
(LabelRepository acts in the same manner)
How it can be fixed?
Update:
After changing fetch strategy to Lazy
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Rule rule;
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Label label;
infinite loop problem has gone, but new one has appeared - related entities are not being populated and when Hibernate is trying to insert values into link table
Hibernate: insert into rule_label (rule_id, label_id) values (?, ?)
we get
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'rule_id' cannot be null
Okay, well I've always used an EmbeddableId for link entities with JPA. I haven't tried the hibernate example you refer to in terms of using cascade to do the work for me. It could be interesting but there are some differences between pure JPA and Spring Data Repositories. By using an EmbeddableId you can create a separate spring repository for the link entity. Then you manage the relationships yourself. If you don't want to do that then you should use a ManyToMany annotation, but the link entity allows you to create link entity attributes, not shown here. This code will work for you and get you to point B and you can experiment from there:
#Entity
public class Label {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.labelId")
private List<RuleLabel> rules = new ArrayList<>();
#Entity
public class Rule {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.ruleId")
private List<RuleLabel> labels = new ArrayList<>();
#Entity
public class RuleLabel {
#EmbeddedId
private RuleLabelId ruleLabelId;
#SuppressWarnings("serial")
#Embeddable
public class RuleLabelId implements Serializable {
private Long ruleId;
private Long labelId;
public interface RuleRepository extends JpaRepository<Rule, Long> {
#Query("from Rule r left join fetch r.labels where r.id = :id")
public Rule getWithLabels(#Param("id") Long id);
}
public interface RuleLabelRepository extends JpaRepository<RuleLabel, RuleLabelId> {}
and to use it:
Rule rule = new Rule();
Label label = new Label();
ruleRepo.save(rule);
labelRepo.save(label);
RuleLabel ruleLabel = new RuleLabel();
RuleLabelId ruleLabelId = new RuleLabelId();
ruleLabelId.setRuleId(rule.getId());
ruleLabelId.setLabelId(label.getId());
ruleLabel.setRuleLabelId(ruleLabelId);
ruleLabelRepo.save(ruleLabel);
rule = ruleRepo.getWithLabels(1L);
System.out.println(rule + Arrays.toString(rule.getLabels().toArray()));
Yes because its what you are telling hibernate to do.
By default, all #ManyToOne and #OneToOne associations are EAGER loaded, so when it querying Rule then its also querying RuleLabel and then inside there is Rule again which is causing infinite select queries. It's better to have them LAZY loaded.
You can do field lazy load like this #ManyToOne(fetch=FetchType.LAZY)
This is what JPA 2.0 spec say about defaults:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
A good read on Lazy and Eager loading

JPAQuery join intermediate many to many table

I'm using QueryDSL JPA, and want to do a join between two tables. I found many similar questions here already, but my case is different from all of them by one little detail. Here is a simplified version of my classes:
#Entity(name = "TABLE_A")
public class TableA {
#Id
#Column(name = "ID_A", nullable = false)
private Long idA;
}
#Entity(name = "TABLE_B")
public class TableB {
#Id
#Column(name = "ID_B", nullable = false)
private Long idB;
}
#Entity(name = "TABLE_C")
public class TableC {
#Id
#Column(name = "ID_C", nullable = false)
private Long idC;
#JoinColumn(name = "ID_A", referencedColumnName = "ID_A")
#ManyToOne
private TableA tableA;
#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")
#ManyToOne
private TableB tableB;
}
Now what I want to do is join Tables A, C and B, to find the Bs which are linked to A. I know this seems like a useless step between, why not add a relation from A to B directly. In my case this is needed, these are just example classes to illustrate.
I tried this:
QTbTableA tableA = QTbTableA.tableA;
QTbTableB tableC = QTbTableC.tableC;
JPAQuery query = new JPAQuery(entityManager).from(tableA);
query.leftJoin(tableA, tableC.tableA);
The join throws an Exception because tableC.tableA is not a root path, only a property. But how do I join these tables correctly then?
Thanks in advance!
If you want to keep your current impl, you could start from TableC and then join the other tables:
query.from(tableC)
.innerJoin(tableC.tableA, tableA)
.innerJoin(tableC.tableB, tableB)
.where(tableA.idA.eq(myId)
.list(tableB);

Hibernate: using shared primary key in HQL

I'm have a problem with sql created by Hibernate when I use entity mapped shared primary key. I'm using JPA 2.1 and Hibernate 5.2.2
Here's my entities:
#Entity
#Column(name = "employee_table")
public class EmployeeEntity {
#Id
#Column(name = "id")
#SequenceGenerator
#GeneratedValue
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private EmployeeDetailsEntity employeeDetailsEntity;
}
#Entity
#Table(name = "employee_details")
public class EmployeeDetailsEntity {
#Id
#Column(name = "id")
private Long id;
// additional attributes
}
I want to select all employees which has details:
select e from EmployeeEntity e where e.employeeDetailsEntity is not null;
Select that was generated by Hibernate is:
select employeeen0_.id from employee_table employeeen0_ where employeeen0_.id is not null;
Could you please explaine me what I'm doing wrong and help to solve this?
It looks like you're missing the "other side" of the Employee -> EmployeeDetails relationship mapping:
Employee entity:
private EmployeeDetails employeeDetails;
#OneToOne(mappedBy = "employee", fetch = FetchType.LAZY)
public EmployeeDetails getEmployeeDetails() {
return employeeDetails;
}
EmployeeDetails entity:
private Employee employee;
#OneToOne(optional = false)
#JoinColumn(name = "EMPLOYEE_ID")
public Employee getEmployee() {
return employee;
}
Thanks to all who comments.
The problem is that #PrimaryKeyJoinColumn on EmployeeDetailsEntity using EmployeeEntity primary key and when in your hql you check that EmployeeDetailsEntity in null (or not null) for native sql Hibernate use id of EmployeeEntity because you are shared this PK with another (EmployeeDetailsEntity) entity.
If you don't want (can't) to change you mapping use join in your hql queries or change #OneToOne mapping.

Annotation to join columns with OR condition instead of AND condition

I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.

Categories