I have two entities :
RawDeviceMessage which represents a raw message from a device
TagDetail which represents the message after being parsed
A TagDetail may or may not be associated with a RawDeviceMessage, because it may be created directly without a raw message to parse. Thus, I have a optional bi-directional OneToOne relation between RawDeviceMessage and TagDetail.
In the database I have the following tables :
raw_device_message (id + other columns)
tag_detail (id + other columns)
tag_detail_has_raw_device_message (tag_detail_id , raw_device_message_id) : this table is a JoinTable with the proper SQL constraints and foreign keys to enforce the OneToOne relation at the database level.
I have mapped my Java classes like that :
RawDeviceMessage
#Entity
#Table(name = "raw_device_message")
public class RawDeviceMessage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#OneToOne(mappedBy = "rawDeviceMessage", fetch = FetchType.LAZY)
private TagDetail tagDetail;
public RawDeviceMessage(){}
public Long getId(){...}
public void setId(final Long id){...}
public TagDetail getTagDetail(){...}
public RawDeviceMessage setTagDetail(TagDetail tagDetail){...}
}
TagDetail
#Entity
#Table(name = "tag_detail")
public class TagDetail implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.REFRESH, CascadeType.MERGE })
#JoinTable(
name="tag_detail_has_raw_device_message",
joinColumns=#JoinColumn(name="tag_detail_id"),
inverseJoinColumns=#JoinColumn(name="raw_device_message_id"))
private RawDeviceMessage rawDeviceMessage;
public TagDetail() {}
public Long getId(){...}
public void setId(final Long id){...}
public RawDeviceMessage getRawDeviceMessage(){...}
public void setRawDeviceMessage(RawDeviceMessage rawDeviceMessage){...}
}
The issue
My issue is that when performing a find all on the RawDeviceMessage resource, Hibernate generates the wrong SQL query :
SELECT rawdevicem0_.id AS id1_15_,
rawdevicem0_2_.tag_detail_id AS tag_deta0_37_,
FROM raw_device_message rawdevicem0_
LEFT OUTER JOIN tag_detail_has_raw_device_message rawdevicem0_2_ ON rawdevicem0_.id=rawdevicem0_2_.tag_detail_id
CROSS JOIN tag_detail tagdetail1_
LEFT OUTER JOIN tag_detail_has_raw_device_message tagdetail1_1_ ON tagdetail1_.id=tagdetail1_1_.tag_detail_id
WHERE rawdevicem0_2_.tag_detail_id=tagdetail1_.id
ORDER BY rawdevicem0_.id ASC
As you can see, in the first LEFT OUTER JOIN, the join condition is rawdevicem0_.id=rawdevicem0_2_.tag_detail_id
It tries to join raw_device_message.id with tag_detail_has_raw_device_message.tag_detail_id , which makes no sense and messes up with all the results.
Instead the join condition should be, rawdevicem0_.id=rawdevicem0_2_.raw_device_message_id
This condition would correctly join raw_device_message.id with tag_detail_has_raw_device_message.raw_device_message_id
I have shortened the query generated by hibernate to remove all unrelated fields, but in the generated query there is nowhere the column raw_device_message_id, so there is definitely something wrong.
Is it an hibernate bug or am I doing my mapping wrong ?
If the purpose of tag_detail_has_raw_device_message table is only to link the two tables, then you can drop it. You can have One-to-One with just the two tables.
More details here -
Setting up a One To ManyJoins Against a Bridge Table using JPA
However if you want to have an intermediate mapping table, because it has some additional info for that relationship, then more details here.
http://what-when-how.com/hibernate/advanced-entity-association-mappings-hibernate/
Related
I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee
I have tables:
users (id, name, email, password)
user_statuses (user_id, is_premium, is_advanced, user_rank_id)
user_ranks (id, name, ordinal)
So the relation between User and UserStatus is 1-1, and I have following entity clasess:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserStatus status;
}
#Entity
#Table(name = "user_statuses")
#Getter
#Setter
#NoArgsConstructor
public class UserStatus {
#Id
private long id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
private boolean isPremium;
private boolean isAdvanced;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_rank_id")
private UserRank rank;
}
#Entity
#Table(name = "user_ranks")
#Getter
#Setter
#NoArgsConstructor
public class UserRank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private int ordinal;
}
Then i created endpoint "/users/{id}" which should return user's email address as a string:
#GetMapping("/users/{id}")
public String getUserEmail(#PathVariable("id") long userId) {
User user = service.getUser(userId);
return user.getEmail();
}
When I call above endpoint I get user's email address as a response, however looking at the console log I see that hibernate executed 2 queries but noone asked him to do so:
First one for fetching the user:
SELECT
user0_.id AS id1_2_0_,
user0_.email AS email2_2_0_,
user0_.name AS name3_2_0_,
user0_.password AS password4_2_0_
FROM
users user0_
WHERE
user0_.id = 1;
And second one for fetching User Status that is associated with this User object:
SELECT
userstatus0_.user_id AS user_id1_1_0_,
userstatus0_.is_advanced AS is_advan2_1_0_,
userstatus0_.is_premium AS is_premi3_1_0_,
userstatus0_.user_rank_id AS user_ran4_1_0_
FROM
user_statuses userstatus0_
WHERE
userstatus0_.user_id = 1;
So I am confused: Why is hibernate running second query when I set fetch = FetchType.LAZY on each relation... It looks like that LAZY is ignored for #OneToOne annotation?
I do not use EntityGraph.
How to stop hibernate for running second query?
EDIT
So, it turns out Hibernate ignores my Lazy hint because it needs to decide should it initialize property with NULL or ProxyObject which makes sense. This link explains it well:
https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/
However this link also suggests that the best way to model this is Unidirectional One to One and it says that I can always fetch UserStatus based on User's ID (because both tables "shares" primary key)
However this confuses me a little bit, because I can fetch both rows using single query (SELECT * FROM users LEFT JOIN user_statuses ON users.id = user_statuses.user_id), but with approach described in the link I need 2 queries, and as far as I know (which I might be wrong) is 1 query is better than executing 2 queries, also if I want to fetch 25 users and their User Statuses, then I would also need 2 queries, one for fetching users and then fetching corespoinding user statuses and finally write nested for each loops to join these objects. I could have just executed one single query to fetch everything...
It is possible to make OTO lazy even if it's not the owning side. You just need to mark it as optional = false. This way Hibernate will know that it can safely a create proxy (and null is not possible) as the association always exists. Note, though it really must be non-optional - the 2nd entity must always exist. Otherwise you'll get an exception once Hibernate tries to load it lazily.
As for the number of queries, with native Hibernate (not JPA!) you can select org.hibernate.annotations.FetchMode. Which gives options to:
Use a separate select
Or use a join to load association
Alternatively, you can stay with JPA and write a JPQL query and use fetch join to keep it as a single query.
PS: before doing additional select Hibernate will check if the element already exists within the Session. If it is, then no select is going to be issued. But with fetch join or FetchMode.JOIN you won't have this luxury - join will always happen.
For one to one relation in hibernate it is always loading reference object whether you keep Fetch type Lazy or Eager. So alternate solution is select only those columns which are needed, it should not contain that reference column. So in this case hibernate will not fire another query.
Query for below class will be :
#Query("select new Example(id,field1) from Example")
#Entity
#Table(name = "example")
class Example implements Serializable {
private static final long serialVersionUID = 1L;
public Example(Long id, String field1) {
this.id = id;
this.field1 = field1;
}
#Id
#Column(name = "id", nullable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "example", fetch = LAZY, cascade = ALL)
private CustomerDetails customerDetails;
#Column(name = "field1", nullable = false, updatable = false)
private String field1;
}
I've been bashing my head on my keyboard for two days trying to figure this out...
Some background: We have a data model set up with a Perl code base that runs straight native SQL statements to the database via ODBC. For certain reasons, we decided to rewrite the code in Java... I thought it would be a good idea to use Hibernate to define all of the mappings. We don't want to edit the data model.
For simplicity sake, I can express the problem with only part of our data model. We have the entities "Job","JobDatabase" and "JobTable".
Job has a PK of job_name. Database has a PK of job_name,name. Table has a PK of job_name,src_database_name,name. As you may expect, Job has a OneToMany relationship with JobDatabase, and Database has a OneToMany with JobTable.
For purposes of this test, I'm starting with empty tables and trying to create some sample data. I can insert a Job and a JobDatabase, but when I try to insert the JobTable, Hibernate throws an error. Or more accurately, that is where it complains. It doesn't start executing my code because it detects the mapping error. However, if I remove the association between JobDatabase and JobTable, it will insert all Job and JobDatabase records correctly with no errors.
Sample Classes (all fields have getters/setters... there are also many other fields):
#Entity
#Table(name="Job")
public class Job implements Serializable {
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "job_name", referencedColumnName = "job_name")
private Set<JobDatabase> databases;
}
#Entity
#Table(name="JobDatabase")
public class JobDatabase implements Serializable {
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#Id
#Column(name="name",nullable = false)
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "job_name", referencedColumnName = "job_name"),
#JoinColumn(name = "name", referencedColumnName = "src_database_name")
})
private Set<JobTable> tables;
}
#Entity
#Table(name="JobTable")
public class JobTable implements Serializable{
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#Id
#Column(name="src_database_name",nullable = false)
private String srcDatabaseName;
#Id
#Column(name="name",nullable = false)
private String name;
}
The error:
Exception in thread "main" org.hibernate.MappingException: Unable to find column with logical name: src_database_name in JobDatabase
I keep getting this error. I do not understand why it is looking for the referenced column in the entity "owning" the mapping. src_database_name does indeed only exist in JobTable - it is referred to as "name" in JobDatabase. JobTable also has a "name" field, but it refers to the name of the Table.
You need to have src_database_name column in your JobDatabase table. Or you can change src_database_name to other column name.
For composite key reference column must be present in your source table.
I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.
I have problem with getting rows from my database using Hibernate. When I would like to get only one row, I am receiving 20. When I would like to get all of rows from table with about 1.5k rows, I am receiving exactly 15.2k rows.
Entity class of this table has composite primary key.
This is my code for getting all rows:
Criteria criteria = getSession().createCriteria(type);
criteria.setCacheable(true).setCacheRegion(BaseEntity.PACKAGE);
criteria.list();
And this is my Entity class:
#javax.persistence.Entity
#Table(name = "my_table")
public class My extends MyEntity<MyPK> {
#EmbeddedId
private MyPK id;
#Column(name = "text", nullable = false)
protected String text;
#ManyToOne
#JoinColumn(name = "property", nullable = false, insertable = false, updatable = false)
protected Option option;
#Override
public MyPK getId() {
return id;
}
#Override
public void setId(MyPK id) {
this.id = id;
}
//getters and setter
}
And this is MyPK class:
#Embeddable
public class MyPK implements Serializable {
#Column(name = "qwerty")
protected String qwerty;
#Column(name = "property")
protected String property;
//constructors, getters and setters
}
MyEntity class is abstract class with #MappedSuperclass annotation. This is this class header:
#MappedSuperclass
public abstract class MyEntity<T extends Serializable>
What am I doing wrong? Is this problem with EmbeddedId?
EDIT #1
As I have realized this is problem with this:
#ManyToOne
#JoinColumn(name = "property", nullable = false, insertable = false, updatable = false)
protected Option option;
This object contains foreign key to another table. And this another table has reference to another. And this last table has 10 rows for previous table. In the result I am getting rows amount * 10. The problem is probably with Hibernate annotation in my entities.
It looks like you're probably eagerly joining a many-to-one relationship somewhere. The default behavior is that you get one entity for each row returned by the database. If you don't want to change the eager fetching, but do want to remove duplicates in your result, you need to use this ResultTransformer:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
#Embeddable only means that MyPK 's columns will be columns in the My class.
Your problem might be #ManyToOne #JoinColumn(name = "property") since it's the same with "property" in MyPK
You can set the maximum number of results by setting this on the criteria with this method: setMaxResults(int maxResults)
Primary key classes need to define equals() and hashCode(), in terms of the aggregated values (qwerty and property, here). Most likely when process the ResultSet, Hibernate is not seeing the entity keys across multiple rows as equal.
From Section 2.4 of the JPA 2.0 specification (in case it helps):
The primary key class must define equals and hashCode methods. The semantics of value
equality for these methods must be consistent with the database equality for the database types
to which the key is mapped