I am trying to map a relationship with an extra attribute (flag) on a join table, but without explicitly defining this join table in an Entity.
Problem:
Business is assigned to 0 or 1 main country, business also is linked to 0 or more secondary countries.
I have three tables: Business, Country, Business_Country
In my Business, I have a #WhereJoinTable which works quite nice for retrieving:
#OneToOne
#JoinTable(
name="business_country",
joinColumns = #JoinColumn( name="business_id"),
inverseJoinColumns = #JoinColumn( name="country_id")
)
#WhereJoinTable(clause="is_main = 1")
private Country mainCountry;
#OneToMany
#JoinTable(
name="business_country",
joinColumns = #JoinColumn( name="business_id"),
inverseJoinColumns = #JoinColumn( name="country_id")
)
#WhereJoinTable(clause="is_main = 0")
private Set<Country> secondaryCountries = new HashSet<>();
I wonder what the counterpart would be of this #WhereJoinTable to perform saves.
Just creating the entity Business and adding the countries to it gives a constraint violation:
cannot insert NULL into ("TESTSCHEMA"."BUSINESS_COUNTRY"."IS_MAIN")
What I'd like to do is that is_main is set to 1 for mainCountry and to 0 for all entries in secondaryCountries.
I was wondering if there was something possible with a custom #SQLInsert?
You can use #SQLInsert to specify a custom sql insert statement for the association table:
#OneToOne
#JoinTable(
name="business_country",
joinColumns = #JoinColumn( name="business_id"),
inverseJoinColumns = #JoinColumn( name="country_id")
)
#WhereJoinTable(clause="is_main = 1")
#SQLInsert(sql = "insert into business_country (business_id, country_id, is_main) values (?, ?, 1)")
private Country mainCountry;
#OneToMany
#JoinTable(
name="business_country",
joinColumns = #JoinColumn( name="business_id"),
inverseJoinColumns = #JoinColumn( name="country_id")
)
#WhereJoinTable(clause="is_main = 0")
#SQLInsert(sql = "insert into business_country (business_id, country_id, is_main) values (?, ?, 0)")
private Set<Country> secondaryCountries = new HashSet<>();
This and this articles can also be helpful (pay attention to ordering of columns in the insert statements).
Related
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 having below annotation on column in my entity class.
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "INQUIRYID", referencedColumnName="ID", updatable=false, insertable=false)
private MyTable myInquiry;
It was giving below error on runtime.
column with logical name ID not found in entity class
As the referenced column is a primary key, I removed the referencedColumnName attribute and updated to below
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "INQUIRYID", updatable=false, insertable=false)
private MyTable myInquiry;
This works perfectly with Hibernate 5.3 but as soon as I go to hibernate 4 , I see some anomalies.
In hibernate 5 I get this issue only with columns which are referring some ID(PK) of another class. However, in hibernate 4 I see this error for non-pk columns as well.
I start to get the same error for columns which are referring non primary keys.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "AB", referencedColumnName = "AB")
private MyTable someData;
Above code gives me error in hibernate 4
Column with logical name AB not found in entity class.
Here, AB is a non primary key column .So I can't remove referencedColumnName attribute.
Is it purely because of hibernate version or there is a different reason for such behavior ?
Referred this : Similar Issue
There is a bug for hibernate 4.1.7
A workaround for this issue is surrounding the column name with gave accents.
Unable to find column with logical name: id in
org.hibernate.mapping.Table(template) and its related supertables and
secondary tables
#ManyToOne #JoinColumnsOrFormulas({ #JoinColumnOrFormula(column =
#JoinColumn(name = "template", referencedColumnName = "id")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'custom'",
referencedColumnName = "type")) }) This is caused by identifying the
column names within the logicalToPhysical map of
TableColumnNameBinding. Within this map the column names are
surrounded by grave accents (`id`) while the check do a lookup to the
map with plain column name (id).
A workaround for this issue is surrounding the column name with gave
accents.
#ManyToOne #JoinColumnsOrFormulas({ #JoinColumnOrFormula(column =
#JoinColumn(name = "template", referencedColumnName = "`id`")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'custom'",
referencedColumnName = "`type`")) })
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.
I have in project user which have authorities. Authorities types stored in database and linked with user as many to many relation.
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_authority", joinColumns = #JoinColumn(name = "uauth_user"), inverseJoinColumns = #JoinColumn(name = "uauth_authority"))
private Set<Authority> userAuthorities;
But when I try to get all authorities of selected user I obtain just one of them. Hibernate just get first of them and put it to list, but ignore all other authorities of user.
I already check database and it store this data. Also I found solution with adding one not JPA annotations. It works with #Fetch(FetchMode.SUBSELECT) but I still don't understand what is wrong with it.
The following solution works on any JPA 2.0 - compliant implementation.
Table user has primary key id.
Table authority has primary key id.
Table user_authority has fields user_id, authority_id.
Entity class User
#JoinTable(name = "user_authority",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "authority_id", referencedColumnName = "id")})
#ManyToMany(fetch = FetchType.EAGER)
private Set<Authority> authoritySet;
Entity class Authority
#ManyToMany(mappedBy = "authoritySet", fetch = FetchType.EAGER)
private Set<User> userSet;
The Table user_authority doesn't have an Entity represention in JPA.
Ok problem was in different place.
My fault was that when I tried it with Hibernate annotation and it is just works I started thinking that this is an annotation problem, but actually it was caused with method of obtaining this values.
While refactoring we leae in code one mistake:
return (T) criteria.setMaxResults(1).uniqueResult();
He we set maximal count of results as 1 and in our case it converted to SQL as
SELECT * FROM user OUTER JOIN user_authority ON usr_id = uauth_user INNER JOIN authority ON uauth_authority = auth_id LIMIT 1
This limit removes all authorities, and leave just first authority from first line. But when we specify Hibernate FetchMode as SUBSELECT. It execute this get in two separate SQL queries. In which just main have limit.
I have the following tables:
[ table : column1, column2 ]
A : id, name
B : id, name
AB : idA, idB
AB is a join table.
Then I have this method on hibernate class B
#OneToMany( fetch = FetchType.EAGER)
#JoinTable( name = "AB",
joinColumns = #JoinColumn( name = "idB"),
inverseJoinColumns = #JoinColumn( name = "idA") )
public List<A> getAs(){
//return the list of matching stuff
}
This works perfectly fine.
Now I want to do this sql query in hibernate:
select * from B inner join AB on B.id = AB.idB where AB.idA = 1234
Essentially, 'list me all B's that reference A with id 1234'
I could do straight sql, but that would defeat the purpose of getAs()
Is it possible to construct a Criterion/Restriction clause to achieve this?
Relationship between A and B is not one-to-many in this case, but rather many-to-many. You should map it as such:
#ManyToMany
#JoinTable(name = "AB",
joinColumns = #JoinColumn( name = "idB"),
inverseJoinColumns = #JoinColumn( name = "idA") )
public List<A> getAs(){
//return the list of matching stuff
}
Note that eagerly fetching a collection is not a good idea in most cases, hence fetch = FetchType.EAGER removed above. You can now do the same on A side to make relationship bidirectional:
#ManyToMany(mappedBy='As')
public List<B> getBs(){
//return the list of matching stuff
}
Now getting all Bs for given A is just a matter of calling getBs() on that A instance. You can create criteria / write HQL to do that as well - from either side.