I have following entities:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "company")
private List<Score> scores;
#JoinTable(name = "company_factor", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "factor_id") )
#ManyToOne(fetch = FetchType.LAZY)
private Factor factors;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "company")
private List<Score> scores;
#JoinTable(name = "employee_factor", joinColumns = #JoinColumn(name = "employee_id") , inverseJoinColumns = #JoinColumn(name = "factor_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Factor> factors;
#Transient
private int score;
}
Factor.class doesn't contain any relationships.
Also, I have some entity Score that is unique for each combination Company-Employee.
It looks like that:
Score.class:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "company_id", insertable = false, updatable = false)
private Company company;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "employee_id", insertable = false, updatable = false)
private Employee employee;
#Column(name = "score")
private BigDecimal score;
If I get List, it will be a list of combinations Company and Employee instances, and sometimes Company or Employee can be repeated.
The goal is to get List, filtered by Factor in Employee and showing only minimal score for each employee ordered in ascending order.
Say, if exist combinations
employee1-company1, score=1
employee1-company2, score=2
employee2-company1, score=3
employee2-company4, score=5
employee3-company4, score6
ResultList should look like that:
employee1-company1, score=1
employee2-company1, score=3
employee3-company4, score=6
So employee should not repeat, but company could repeat in the List.
I am not quite sure, how to do that. What I achieved is showing unique results in ascending order, but they don't show min score. I used HQL:
select distinct e from Score e
left outer join fetch e.company
left outer join fetch e.company.factors
left outer join fetch e.employee
left outer join fetch e.employee.factors ef
where ef.factor_id=:factor_id
group by e.employee.employee_id
order by e.score asc
Could anybody help how to achieve what I need? Thank you.
UPDATE1:
I decided to go another way.
Now I am getting it via Employee using this query:
select distinct e from Employee e join e.scores es order by es.score asc
It seems it's exactly what I need, but how to put in the query the minimum es.score to the field score of Employee object? Maybe there is some way to substitute e.score by es.score?
You can use the following:
Select a from (Select b from Score b order by score) as a group by a.employee
Explanation:
Select b from Score b order by score : gets the results in asc (default) order of score
Group by on above result will give the unique employees
As a solution I switched to entityManager.createNativeQuery("some native sql string").
I am satisfied with the result. Just for the case, question about SQL query is here
The only drawback is that it's impossible to use join fetch, hence N+1 select problem is here, but I plan to fetch quite small chunk of data, so it's bearable.
Related
I had a db with tables SPEC and PARTS.Also I had a table for MANY TO MANY relations. In my project I used spring jdbs template and all works good. Then I decide to change jdbc on SPring data jpa.
My Entities:
#Entity
#Table(name = "PARTS")
public class PartsJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_EXPORT", unique = false, nullable = false, updatable = true)
private ExportJpa exportJpa;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_TYPE", unique = false, nullable = false, updatable = true)
private TypesJpa typesJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<SpecJpa> specJpa;
////////
}
And Spec:
#Entity
#Table(name = "SPEC")
public class SpecJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "Creator_ID", unique = false, nullable = false, updatable = true)
private UsersJpa usersJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<PartsJpa> partsJpa;
////////////////
}
I don't show getters and setters.
It works, but when I start a programm, something in my table was changed and now I can't add in table spec_parts values like(1,3)(1,2).
Mistake:
FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)" Referential integrity constraint violation: "FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)"; SQL statement: INSERT INTO "PUBLIC"."SPEC_PARTS"("ID_SPEC","ID_PARTS")VALUES(?,?)
Maybe I have mistake with creating relations between spec and parts? What problem it can be?
data in spec
ID NAME CREATOR_ID DESCRIPTION CHANGER_ID
1 pc 1 description 1
2 pc2 2 description2 2
data in parts
ID ▼ NAME ID_EXPORT ID_TYPE DESCRIPTION
1 intel core i5 1 1 d1
2 intel core i7 1 1 d2
3 ddr3 2 2 d3
4 ddr4 2 2 d4
5 asus 3 3 d5
data in spec_parts now:
ID_SPEC ID_PARTS
1 1
2 2
so I can't add 1,3 or 2,4
I find a problem, spring date change something and now in table SPEC_PARTS ID_SPEC mapping on PARTS.ID. Why?
As you are using ManyToMany relation, there is a mapping table created named SPEC_PARTS which have referenced columns ID_SPEC and ID_PARTS.These columns value come from SPEC.ID and PARTS.ID. So you can't insert in SPEC_PARTS without creating referenced value because you are trying to do foreign key constraint violation.
Suppose if there is a row in SPEC with id value 1 and there is a row in PARTS with id value 2. Then you can insert in SPEC_PARTS with value like (1,2).
So, first, add data in SPEC and PARTS then map them in SPEC_PARTS.
And you can remove #JoinTable from one side, you don't need to define it both side.
Update:
Problem is SpecJpa class relation. Here you are using SPEC_PARTS.ID_SPEC as foriegn key for PARTS.ID and SPEC_PARTS.ID_PARTS as foriegn key for SPEC.ID which is fully reversed what you do in PartsJpa class.
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
That's why this error say
SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)";
There is no SPEC.ID value 3 exist in the database.
Solution:
Remove #JoinTable from SpecJpa class as you don't need to specify both side.
And remove the wrong relation of the foreign key from database also.
I've got the next database relation
This table defines the id of an entity
ID
1
2
3
4
And this the relation into this table and it self
ID_A ID_B
1 2
2 1
3 1
I've got this mapping in my entity and it works properly
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "AGRUPACION_MOTIVOS_INDEBIDO", joinColumns = #JoinColumn(name = "ID_MOTIVO_INDEBIDO_A", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "ID_MOTIVO_INDEBIDO_B", referencedColumnName = "ID"))
private List<MotivoIndebido> motivosA = new ArrayList<>();
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "AGRUPACION_MOTIVOS_INDEBIDO", joinColumns = #JoinColumn(name = "ID_MOTIVO_INDEBIDO_B", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "ID_MOTIVO_INDEBIDO_A", referencedColumnName = "ID"))
private List<MotivoIndebido> motivosB = new ArrayList<>();
But i need to combine this two list in one with all results with no duplicate cases. I mean:
The result list should contain something like:
ID_A IB_B
1 2
1 3
Can i accomplish this with hibernate or jpa or i need to combine this list manually in a java method?.
And then i should save and delete data manually too?
Sorry for my english and thanks in advice.
I am using spring with hibernate to store data in MySql database. I am trying to retrieve rows based on filters requested by the user.
I have the following tables/entities : Product and Gemstone
Relations:
Product many2many Gemstone
I am trying to write a query to get products that have Gemstone A and Gemstone B and Gemstone C.. and so on.
Use Case:
If user is asking for a product with gemstones 51 and 46. Query should only return product id 4.
Query:
filterGemstones() method return the gemstone user wants to filter products to. Using the below query I get zero records but if I remove HAVING Count(DISTINCT p.product_id) = 2 I get product id 4, 5
HQL :
createQuery("select p.productId from Product p JOIN p.gemstones g where g in :gemstones group by p having count (distinct p) =" + filterGemstones().size() ).setParameter("gemstones",filterGemstones());
SQL generate by hibernate :
SELECT p.product_id
FROM product p
INNER JOIN gemstone_product gp
ON p.product_id = gp.product_id
INNER JOIN gemstone g
ON gp.gemstone_id = g.gemstone_id
WHERE g.gemstone_id IN ( 51, 46 )
GROUP BY p.product_id
HAVING Count(DISTINCT p.product_id) = 2
Product class:
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private long productId;
#ManyToMany()
#JoinTable(
name = "gemstone_product",
joinColumns = {#JoinColumn(name = "product_id")},
inverseJoinColumns = {#JoinColumn(name = "gemstone_id")}
)
private Set<Gemstone> gemstones = new HashSet<>(0);
// setters and getters
}
Gemstone class:
#Entity
#Table(name = "gemstone")
public class Gemstone {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "gemstone_id")
private long gemstoneId;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "gemstone_product",
joinColumns = {#JoinColumn(name = "gemstone_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")}
)
private Set<Product> products = new HashSet<>(0);
// setters and getters
}
Actually the SQL query that we need here is pretty simple:
SELECT t1.product_id
FROM gemstone_product AS t1
WHERE (t1.gemstone_id IN ?1 ) # (51, 46)
GROUP BY t1.product_id
HAVING (COUNT(t1.gemstone_id) = ?2) # 2 - # of items
It's a bit frustrating that it's not easy to create it with JPA, but it can be done with FluentJPA (produces the query above):
public List<Integer> getProductsContainingAllStones(List<Long> gemstoneIds) {
int count = gemstoneIds.size();
FluentQuery query = FluentJPA.SQL((Gemstone gemstone,
JoinTable<Gemstone, Product> gemstoneProduct) -> {
discardSQL(gemstoneProduct.join(gemstone, Gemstone::getProducts));
long productId = gemstoneProduct.getInverseJoined().getProductId();
long gemstoneId = gemstoneProduct.getJoined().getGemstoneId();
SELECT(productId);
FROM(gemstoneProduct);
WHERE(gemstoneIds.contains(gemstoneId));
GROUP(BY(productId));
HAVING(COUNT(gemstoneId) == count);
});
return query.createQuery(em).getResultList();
}
More details on how it works can be found here.
When mapping two or more columns in innerjoin there is a map to the conditions and
Can I change or a condition?
Parent table
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private List<KeyboxDept> keyboxDept;
Child table
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "KEY_TARGET_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false),
#JoinColumn(name = "KEY_TARGET_UPPER_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false)
})
private User user;
Query is made
select
user0_.user_id as user_id1_3_,
user0_.dept_id as dept_id2_3_,
user0_.posi_id as posi_id3_3_
from
cm_user user0_
inner join
cm_keybox_dept keyboxdept2_
on user0_.dept_id=keyboxdept2_.key_target_id
and user0_.dept_id=keyboxdept2_.key_target_upper_id
where
user0_.user_id=? limit ?
Can i switch and -> or ???
I am not sure about JPA but In Hibernate Using Criteria we can do this....
Criteria criteria = session.createCriteria(persistentClass);
criteria.createCriteria("propertyName", Criteria.LEFT_JOIN);
You have cascade = CascadeType.ALL in entity definition. That means, The operations that must be cascaded to the target of the association. So the target (KeyboxDept in your code) must exist. If you remove it, means no operations being cascaded, I think the generated sql will have no inner.
If your search criteria is not a foreign key matching to the joined table key then you cannot use this syntax because there is obviously an AND relationship between the separate column parts (#JoinColumn elements) of the foreign key. This is why the generated native SQL contains the AND. If you need the OR, you need to initialize that member with a separate JPQL query which contains the OR and you do not need the annotation #ManyToOne.
I need a jpql query for my Spring repository interface method, to retrieve all Posts for a given Semester.
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable
(
name = "semester_post",
joinColumns = {#JoinColumn(name = "semester_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "post_id", referencedColumnName = "id", unique = true)}
)
private List<PostEntity<?>> posts = new ArrayList<>();
PostEntity doesn't have a reference to Semester, and I do not want to add one, because I plan to use this PostEntity for other things than Semester. Maybe I'll have another class (let's say Group) which will also have a OneToMany of PostEntity (like the one in Semester)
So, how do I write this SQL query as a JPQL one ?
select * from posts join semester_post on semester_post.post_id = posts.id where semester_post.semester_id = 1;
My repository
public interface PostRepository extends JpaRepository<PostEntity, Long> {
String QUERY = "SELECT p FROM PostEntity p ... where semester = :semesterId";
#Query(MY_QUERY)
public List<PostEntity> findBySemesterOrderByModifiedDateDesc(#Param("semesterId") Long semesterId);
A query which will get you the result that you need is:
SELECT p FROM SemesterEntity s JOIN s.posts p WHERE s.id = :semesterId
This query uses the JOIN operator to join the SemesterEntity to the PostEntity across the posts relationship. By joining the two entities together, this query returns all of the PostEntity instances associated with the relevant SemesterEntity.