I am facing a very strange issue with hibernate criteria in my application. Below mentioned in the snippet from my source code.
Entity Class
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
#Entity
#Table(name = "AIRPORT")
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Airport implements Serializable {
private static final long serialVersionUID = -7120581694566566178L;
private Long id;
private String countryCode;
private String countryName;
private String cityCode;
private String cityName;
private String airportCode;
private String airportName;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "COUNTRY_NAME")
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
#Column(name = "COUNTRY_CODE", length = 10)
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
#Column(name = "CITY_CODE", length = 25)
public String getCityCode() {
return cityCode;
}
public void setCityCode(String cityCode) {
this.cityCode = cityCode;
}
#Column(name = "CITY_NAME")
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
#Column(name = "AIRPORT_CODE", unique = true, length = 10)
public String getAirportCode() {
return airportCode;
}
public void setAirportCode(String airportCode) {
this.airportCode = airportCode;
}
#Column(name = "AIRPORT_NAME")
public String getAirportName() {
return airportName;
}
public void setAirportName(String airportName) {
this.airportName = airportName;
}
}
DAO Class
Criteria criteria = getSession().createCriteria(getTemplateClass());
criteria.addOrder(Order.asc("countryCode"));
criteria.addOrder(Order.asc("cityCode"));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.setCacheable(true);
return (List<Airport>) criteria.list();
Generated SQL when starting application and querying result
Hibernate: select this_.ID as ID1_12_0_, this_.AIRPORT_CODE as AIRPORT_2_12_0_, this_.AIRPORT_NAME as AIRPORT_3_12_0_, this_.CITY_CODE as CITY_COD4_12_0_, this_.CITY_NAME as CITY_NAM5_12_0_, this_.COUNTRY_CODE as COUNTRY_6_12_0_, this_.COUNTRY_NAME as COUNTRY_7_12_0_ from AIRPORT this_ order by this_.COUNTRY_CODE asc, this_.CITY_CODE asc
If I call same code again and suppose I have 1000 airports list then it executes below query for 1000 times. This behavior is quite strange.
Hibernate: select airport0_.ID as ID1_12_0_, airport0_.AIRPORT_CODE as AIRPORT_2_12_0_, airport0_.AIRPORT_NAME as AIRPORT_3_12_0_, airport0_.CITY_CODE as CITY_COD4_12_0_, airport0_.CITY_NAME as CITY_NAM5_12_0_, airport0_.COUNTRY_CODE as COUNTRY_6_12_0_, airport0_.COUNTRY_NAME as COUNTRY_7_12_0_ from AIRPORT airport0_ where airport0_.ID=?
Hibernate: select airport0_.ID as ID1_12_0_, airport0_.AIRPORT_CODE as AIRPORT_2_12_0_, airport0_.AIRPORT_NAME as AIRPORT_3_12_0_, airport0_.CITY_CODE as CITY_COD4_12_0_, airport0_.CITY_NAME as CITY_NAM5_12_0_, airport0_.COUNTRY_CODE as COUNTRY_6_12_0_, airport0_.COUNTRY_NAME as COUNTRY_7_12_0_ from AIRPORT airport0_ where airport0_.ID=?
Hibernate: select airport0_.ID as ID1_12_0_, airport0_.AIRPORT_CODE as AIRPORT_2_12_0_, airport0_.AIRPORT_NAME as AIRPORT_3_12_0_, airport0_.CITY_CODE as CITY_COD4_12_0_, airport0_.CITY_NAME as CITY_NAM5_12_0_, airport0_.COUNTRY_CODE as COUNTRY_6_12_0_, airport0_.COUNTRY_NAME as COUNTRY_7_12_0_ from AIRPORT airport0_ where airport0_.ID=?
........
........
Even I am using ehcache and even the below line in my criteria.
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
Any help would be greatly appreciated.
I can think of a few different reasons why this might be occuring:
Your entity has an association defined in it that is configured to eager join by default and you have also specified that the association use FetchMode.SELECT. (This is known as the N+1 Problem)
While the transaction is still open, you are interacting with an association of each Airport object that is set to lazy load. By interacting, I mean, you are using a getter to access the relation, forcing Hibernate to deproxy the associated entity. Since the deproxying is occurring with the transaction still open and the associated entity has not yet been loaded, Hibernate automatically fetches the association for you.
You have written your Airport entity's hashcode or equals methods to use a property of an association that is not eagerly joined and forces hibernate to deproxy, and thus fetch the unloaded entity, while within the transaction.
Related
So basically I have the following need. A person is going to POST an entity called "Expense Report" through a Rest Controller.
That entity has a field Country that is actually an association with another entity.
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "EXPENSE_REPORTS")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "COUNTRY_ID")
private Country country;
#Column(name = "TRIP_START_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripStartDate;
#Column(name = "TRIP_END_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripEndDate;
#Column(name = "TOTAL_AMOUNT")
private BigDecimal totalAmount;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="USER_ID")
private User user;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="EXPENSE_ITEM_ID")
private Set<ExpenseItem> expenses;
}
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "COUNTRIES")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Country extends BaseEntity {
#Column(name = "NAME")
#NaturalId
private String name;
}
What I wanted is to check, if it is possible that the caller just sends the natural id of the country (in this case, the name) and JPA rather than trying to insert this country, he finds the country with the same name and associates it.
So, rather than sending this post:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"id": 12345
}
}
He sends this:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"name": "BR"
}
}
I can imagine how to do this either in a repository implementation, but I was wondering if there is something automatic to do this in JPA.
Thanks in advance.
As far as I could see, there is no automatic JPA way of doing this. I know that is not much of an answer, but I also do not think it is likely that there will be a way to do this automatically in JPA (for now), since Hibernate is very focussed on using primary keys and foreign keys (i.e. surrogate IDs).
As "curiousdev" mentions in the comments, using a country code like "BR" (which can be non null unique, just like a primary key) as the key (or joincolumn) is a good alternative in this case. There is a whole discussion about it here if you are interested.
For my own interest, I did some digging in how a repository implementation could look when using the surrogate ID and natural ID. The combination of second level cache and a reference lookup looks promising. You can avoid an extra select-query in that case. The code below is runnable (with the required depencencies in place) and shows what I found so far.
The reference I'm talking about is in the line s.byNaturalId(Country.class).using("code", "NL").getReference();.
The cache is in the settings (hibernate.cache.use_second_level_cache and hibernate.cache.region.factory_class) and the annotations #org.hibernate.annotations.Cache and #NaturalIdCache.
// package naturalid;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.jcache.internal.JCacheRegionFactory;
import org.hibernate.dialect.H2Dialect;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Using natural ID to relate to an existing record.
* <br>https://stackoverflow.com/questions/60475400/select-entity-with-the-informed-natural-id-rather-than-trying-to-insert-jpa-an
* <br>Dependencies:<pre>
* org.hibernate:hibernate-core:5.4.12.Final
* org.hibernate:hibernate-jcache:5.4.12.Final
* org.ehcache:ehcache:3.8.1
* com.h2database:h2:1.4.200
* org.slf4j:slf4j-api:1.7.25
* ch.qos.logback:logback-classic:1.2.3
* org.projectlombok:lombok:1.18.4
* </pre>
*/
#Slf4j
public class NaturalIdRel {
public static void main(String[] args) {
try {
new NaturalIdRel().test();
} catch (Exception e) {
log.error("Tranactions failed.", e);
}
}
void test() throws Exception {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#bootstrap
Map<String, Object> settings = new HashMap<>();
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations
settings.put("hibernate.dialect", H2Dialect.class.getName());
settings.put("hibernate.connection.url", "jdbc:h2:mem:test;database_to_upper=false;trace_level_system_out=2");
settings.put("hibernate.connection.username", "SA");
settings.put("hibernate.connection.password", "");
settings.put("hibernate.hbm2ddl.auto", "create");
settings.put("hibernate.show_sql", "true");
settings.put("hibernate.cache.use_second_level_cache", "true");
settings.put("hibernate.cache.region.factory_class", JCacheRegionFactory.class.getName());
settings.put("hibernate.cache.ehcache.missing_cache_strategy", "create");
settings.put("hibernate.javax.cache.provider", EhcacheCachingProvider.class.getName());
settings.put("hibernate.javax.cache.missing_cache_strategy", "create");
//settings.put("", "");
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.applySettings(settings)
.build();
Metadata md = new MetadataSources(ssr)
.addAnnotatedClass(ExpenseReport.class)
.addAnnotatedClass(Country.class)
.buildMetadata();
SessionFactory sf = md.getSessionFactoryBuilder()
.build();
try {
createCountry(sf);
createExpense(sf);
} finally {
sf.close();
}
}
void createCountry(SessionFactory sf) {
Country c = new Country();
c.setCode("NL");
try (Session s = sf.openSession()) {
save(s, c);
}
}
void createExpense(SessionFactory sf) {
ExpenseReport er = new ExpenseReport();
er.setDescription("Expenses");
er.setReason("Fun");
// Watch (log) output, there should be no select for Country.
try (Session s = sf.openSession()) {
// https://www.javacodegeeks.com/2013/10/natural-ids-in-hibernate.html
Country cer = s.byNaturalId(Country.class).using("code", "NL").getReference();
er.setCountry(cer);
save(s, er);
}
}
void save(Session s, Object o) {
Transaction t = s.beginTransaction();
try {
s.save(o);
t.commit();
} finally {
if (t.isActive()) {
t.rollback();
}
}
}
#Entity
#Data
static class ExpenseReport {
#Id
#GeneratedValue
int id;
#Column
String description;
#Column
String reason;
#ManyToOne
// Can also directly map country code.
// https://stackoverflow.com/questions/63090/surrogate-vs-natural-business-keys
Country country;
}
#Entity
#Data
// https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/
#org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE
)
#NaturalIdCache
static class Country {
#Id
#GeneratedValue
int id;
#NaturalId
String code;
}
}
Use association like below, instead of COUNTRIES.COUNTRY_ID use COUNTRIES.NAME
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “NAME”,referencedColumnName = "name" ) //Do not use COUNTRY_ID
private Country country;
….
}
public class Country extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "NAME”,nullable = false, updatable = false, unique = true)
#NaturalId(mutable = false)
private String name;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Country)) {
return false;
}
Product naturalIdCountry = (Country) o;
return Objects.equals(getName(), naturalIdCountry.getName());
}
#Override
public int hashCode() {
return Objects.hash(getName());
}
}
SQL [n/a]; constraint [PRIMARY];
I created a CRUD RESTful API application using Java/Spring Boot. I have a MariaDB with a list of employees and relevant data inside. I am using MySQL Workbench as my SQL Client.
There are no errors when running the Spring Boot application. No errors in my database. No errors when I get the API of all Engineers in my database.
My "GET" requests work fine; however, "POST" does not. I am unable to create a new employee without having to directly insert into the database with MySQL WB.
I want to be able to create a new employee using APIs via Postman.
As you can see there is a ConstraintViolationException being thrown. The contraint is my Primary Key, which is "id." I've set that to auto-increment in MySQL WB.
Here is the code for my Engineer class:
package engineermanagement.model;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
#Entity
#Table(name = "Engineers")
#EntityListeners(AuditingEntityListener.class)
public class Engineer {
private Long id;
private String firstName;
private String lastName;
private String sid;
private String email;
private String manager;
private Boolean teamLead;
private String groupName;
private String shift;
private int startTime;
private int endTime;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "first_name", nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column(name = "last_name", nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(name = "eid", nullable = false)
public String getEid() {
return eid;
}
public void setEid(String eid) {
this.sid = eid;
}
#Column(name = "email", nullable = false)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Column(name = "manager", nullable = false)
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
#Column(name = "teamlead", nullable = false)
public Boolean getTeamLead() {
return teamLead;
}
public void setTeamLead(Boolean teamLead) {
this.teamLead = teamLead;
}
#Column(name = "group_name", nullable = false)
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
#Column(name = "shift", nullable = false)
public String getShift() {
return shift;
}
public void setShift(String shift) {
this.shift = shift;
}
#Column(name = "start_time", nullable = false)
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
#Column(name = "end_time", nullable = false)
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
}
This is the code for my EngineerController class:
package jpmchase.controller;
import jpmchase.exception.*;
import jpmchase.model.Engineer;
import jpmchase.repository.EngineerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#RestController
#RequestMapping("/api/v1")
public class EngineerController {
#Autowired
private EngineerRepository engineerRepository;
#GetMapping("/engineers")
public List<Engineer> getAllEngineers() {
return engineerRepository.findAll();
}
#GetMapping("/engineers/{id}")
public ResponseEntity<Engineer> getEngineerById(
#PathVariable(value = "id") Long engineerId) throws ResourceNotFoundException {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
return ResponseEntity.ok().body(engineer);
}
#PostMapping("/engineers")
public Engineer createEngineer(#Valid #RequestBody Engineer engineer) {
return engineerRepository.save(engineer);
}
#PutMapping("/engineers/{id}")
public ResponseEntity<Engineer> updateEngineer(
#PathVariable(value = "id") Long engineerId,
#Valid #RequestBody Engineer engineerDetails) throws ResourceNotFoundException {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
engineer.setId(engineerDetails.getId());
engineer.setFirstName(engineerDetails.getFirstName());
engineer.setLastName(engineerDetails.getLastName());
engineer.setSid(engineerDetails.getSid());
engineer.setEmail(engineerDetails.getEmail());
engineer.setTeamLead(engineerDetails.getTeamLead());
engineer.setManager(engineerDetails.getManager());
engineer.setShift(engineerDetails.getShift());
engineer.setStartTime(engineerDetails.getStartTime());
engineer.setEndTime(engineerDetails.getEndTime());
// engineer.setUpdatedAt(new Date());
final Engineer updatedEngineer = engineerRepository.save(engineer);
return ResponseEntity.ok(updatedEngineer);
}
#DeleteMapping("/engineers/{id}")
public Map<String, Boolean> deleteEngineer(
#PathVariable(value = "id") Long engineerId) throws Exception {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
engineerRepository.delete(engineer);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}
Using Postman, I am trying to "POST" a new "test" employee but fails due to the aforementioned constraint. Any help will be appreciated...
Full stack trace:
2019-01-11 15:26:50.605 WARN 15276 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2019-01-11 15:26:50.606 ERROR 15276 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '68' for key 'PRIMARY'
2019-01-11 15:26:50.606 ERROR 15276 --- [nio-8080-exec-6] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
2019-01-11 15:26:50.657 WARN 15276 --- [nio-8080-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
I had this same issue and the reason is this
o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '68' for key 'PRIMARY'.
Hibernate is trying to generate an Id for the new Engineer, but when it tries to follow the generation strategy specified by #GeneratedValue(strategy = GenerationType.AUTO) It gets an Id that already exists.
Most likely this happens because you manually added new rows to the Engineer table that collapses with the next generated Id values by Hibernate.
Also if you keep trying to insert a new Engineer multiple times you will end up succeeding since Hibernate increments the Id for the next insertion request until it finds an Id that doesn't already exist.
To solve the issue you can change the Id generation strategy to #GeneratedValue(strategy = GenerationType.IDENTITY) or keep trying to insert until Hibernate get to a non used Id value (If you want to keep the same generation strategy)
org.hibernate.exception.ConstraintViolationException,
The Exception constraint violation means you set database fields with non-null, unique, etc. The database checks the data before inserting it into the table. If any one of the conditions not matching you will get a ConstraintViolationException.
In your case you have a PRIMARY key violation. so check your database table Engineer with "id=68" exists or not. If it is there kindly remove the row.
I have a Criteria query using several joins, and the generated SQL lists the tables out of order so that an ON clause refers to a table that hasn't been declared yet.
To reproduce the problem, I created a small data model with three tables: Bill, Event, and a junction table BillEvent (I've listed a runnable JUnit test with entity definitions at the end of the question). The following Criteria query fails with a syntax error because event1 is declared after it's referenced. How can I rewrite this query so that the tables are declared in the right order?
// Get the most recent BillEvent for a bill
final Criteria criteria = session.createCriteria(BillEvent.class, "be1")
.createCriteria("event", "event1")
.createCriteria("be1.bill")
.add(Restrictions.eq("id", billId))
.createCriteria("billEvents", "be2")
.createCriteria("event", "event2", JoinType.LEFT_OUTER_JOIN,
Restrictions.ltProperty("event1.time", "time"))
.add(Restrictions.isNull("event2.id"));
The error:
Caused by: org.h2.jdbc.JdbcSQLException: Column "EVENT1X1_.TIME" not found; SQL statement:
select
this_.id as id1_1_4_,
this_.billId as billId3_1_4_,
this_.eventId as eventId4_1_4_,
this_.note as note2_1_4_,
hibernatej2_.id as id1_0_0_,
hibernatej2_.label as label2_0_0_,
be2x3_.id as id1_1_1_,
be2x3_.billId as billId3_1_1_,
be2x3_.eventId as eventId4_1_1_,
be2x3_.note as note2_1_1_,
event2x4_.id as id1_2_2_,
event2x4_.time as time2_2_2_,
event1x1_.id as id1_2_3_,
event1x1_.time as time2_2_3_
from
test.billEvent this_
inner join test.bill hibernatej2_ on this_.billId=hibernatej2_.id
inner join test.billEvent be2x3_ on hibernatej2_.id=be2x3_.billId
left outer join test.event event2x4_
on be2x3_.eventId=event2x4_.id
and ( event1x1_.time<event2x4_.time )
inner join test.event event1x1_ on this_.eventId=event1x1_.id
where
hibernatej2_.id=?
and event2x4_.id is null
JUnit test using Hibernate 5 and H2:
package com.stackoverflow.repro;
import static javax.persistence.GenerationType.IDENTITY;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.h2.Driver;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.sql.JoinType;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class HibernateJoinTest {
private static final String TEST_CATALOG = "test";
#Rule public TestName name = new TestName();
#Entity
#Table(name = "bill", catalog = TEST_CATALOG)
public static class Bill implements java.io.Serializable {
private Integer id;
private String label;
private Set<BillEvent> billEvents = new HashSet<BillEvent>(0);
public Bill() {
}
public Bill(String label) {
this.label = label;
}
public Bill(String label, Set<BillEvent> billEvents) {
this.label = label;
this.billEvents = billEvents;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "label", unique = true, nullable = false, length = 45)
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "bill", cascade = { CascadeType.ALL })
public Set<BillEvent> getBillEvents() {
return this.billEvents;
}
public void setBillEvents(Set<BillEvent> billEvents) {
this.billEvents = billEvents;
}
}
#Entity
#Table(name = "event", catalog = TEST_CATALOG)
public static class Event implements java.io.Serializable {
private Integer id;
private Timestamp time;
private Set<BillEvent> billEvents = new HashSet<>(0);
public Event() {
}
public Event(Timestamp time) {
this.time = time;
}
public Event(Timestamp time, Set<BillEvent> billEvents) {
this.time = time;
this.billEvents = billEvents;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "time", nullable = false)
public Timestamp getTime() {
return this.time;
}
public void setTime(Timestamp time) {
this.time = time;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "event", cascade = { CascadeType.ALL })
public Set<BillEvent> getBillEvents() {
return this.billEvents;
}
public void setBillEvents(Set<BillEvent> billEvents) {
this.billEvents = billEvents;
}
}
#Entity
#Table(name = "billEvent", catalog = TEST_CATALOG, uniqueConstraints = #UniqueConstraint(columnNames = {"billId", "eventId"}) )
public static class BillEvent implements java.io.Serializable {
private Integer id;
private Bill bill;
private Event event;
private String note;
public BillEvent() {
}
public BillEvent(Bill bill, Event event) {
this.bill = bill;
this.event = event;
}
public BillEvent(Bill bill, Event event, String note) {
this.bill = bill;
this.event = event;
this.note = note;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinColumn(name = "billId", nullable = false)
public Bill getBill() {
return this.bill;
}
public void setBill(Bill bill) {
this.bill = bill;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinColumn(name = "eventId", nullable = false)
public Event getEvent() {
return this.event;
}
public void setEvent(Event event) {
this.event = event;
}
#Column(name = "note", unique = true, nullable = false, length = 120)
public String getNote() {
return this.note;
}
public void setNote(String note) {
this.note = note;
}
}
#Test
public void testOuterJoin() {
final SessionFactory sessionFactory = createSessionFactory();
final String label = "B0001";
final Timestamp ts = new Timestamp(System.currentTimeMillis());
final Timestamp ts2 = new Timestamp(ts.getTime() + 1000);
final String note1 = "First note";
final String note2 = "Second note";
final int billId;
try (final Session session = sessionFactory.openSession();) {
final Transaction tx = session.beginTransaction();
final Bill bill = new Bill(label);
session.save(bill);
billId = bill.getId();
final Event event1 = new Event(ts);
session.save(event1);
final Event event2 = new Event(ts2);
session.save(event2);
session.save(new BillEvent(bill, event1, note1));
session.save(new BillEvent(bill, event2, note2));
session.flush();
tx.commit();
}
try (final Session session = sessionFactory.openSession()) {
final Criteria criteria = session.createCriteria(BillEvent.class, "be1")
.createCriteria("event", "event1")
.createCriteria("be1.bill")
.add(Restrictions.eq("id", billId))
.createCriteria("billEvents", "be2")
.createCriteria("event", "event2", JoinType.LEFT_OUTER_JOIN,
Restrictions.ltProperty("event1.time", "time"))
.add(Restrictions.isNull("event2.id"));
#SuppressWarnings("unchecked")
final List<BillEvent> results = criteria.list();
Assert.assertEquals(1, results.size());
final BillEvent billEvent = results.get(0);
Assert.assertEquals(note2, billEvent.getNote());
Assert.assertEquals(ts2, billEvent.getEvent().getTime());
}
}
private SessionFactory createSessionFactory() {
final String dialectClassName = H2Dialect.class.getName();
final Configuration config =
new Configuration()
.addAnnotatedClass(Bill.class)
.addAnnotatedClass(Event.class)
.addAnnotatedClass(BillEvent.class);
final String dbName = name.getMethodName();
config.setProperty(Environment.DIALECT, dialectClassName);
config.setProperty(Environment.DRIVER, Driver.class.getName());
config.setProperty(Environment.URL, "jdbc:h2:mem:"+dbName+";DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS TEST\\; SET SCHEMA TEST");
config.setProperty(Environment.USER, "SA");
config.setProperty(Environment.PASS, "");
config.setProperty(Environment.SHOW_SQL, "true");
config.setProperty(Environment.FORMAT_SQL, "true");
final StandardServiceRegistry serviceRegistry = config.getStandardServiceRegistryBuilder().applySettings(config.getProperties()).build();
final MetadataSources sources =
new MetadataSources(serviceRegistry)
.addAnnotatedClass(Bill.class)
.addAnnotatedClass(Event.class)
.addAnnotatedClass(BillEvent.class);
final Metadata metadata = sources.buildMetadata();
final SchemaExport export = new SchemaExport((MetadataImplementor) metadata);
export.create(false, true);
final SessionFactory sessionFactory = config.buildSessionFactory();
return sessionFactory;
}
}
Edit: The issue here seems to be that Hibernate enumerates the tables alphabetically by their propery name. So if there were the following joins:
from root
inner join root.z
inner join root.z.b
inner join root.z.a
inner join root.a on (... and root.z.prop = root.a.prop)
The generated order would be
from root
inner join root.a on (... and root.z.prop = root.a.prop)
inner join root.z
inner join root.z.a
inner join root.z.b
Renaming BillEvent.bill to BillEvent.zBill (or anything alphabetically after event) fixes the syntax error in this query. This is not scalable, though: if you want to query from the other side of the junction table, that query will fail because it's now alphabetically out of order.
When criteria is used hibernate actually traverses entity tree in Depth First Search way to build joins according to fields definition from configuration. In your case BillEvent is traversed bill first, then subfields of Bill class. So basically it creates event entity join after it creates all joins from bill association. You can define the order in hbm.xml but as you mentioned it's not very scalable.
So you have at least two options here:
Change criteria so root entity will be different, then add projection and result transformer to fetch BillEvent entities. For instance:
final Criteria criteria = session.createCriteria(Event.class, "event1")
.createCriteria("event1.billEvents", "be1")
.createCriteria("be1.bill", "bill1")
.createCriteria("bill1.billEvents", "be2")
.createCriteria("be2.event", "event2", JoinType.LEFT_OUTER_JOIN,
Restrictions.ltProperty("event1.time", "event2.time"))
.add(Restrictions.eq("be1.id", billId))
.add(Restrictions.isNull("event2.id"))
.setProjection(Projections.projectionList()
.add(Projections.property("be1.event"), "event")
.add(Projections.property("be1.note"), "note"))
.setResultTransformer(Transformers.aliasToBean(BillEvent.class));
Another possible option is nto use hql rather then criteria api. It will allow you to control join order directly from request since it uses a different sql build mechanism.
Hello brothers and sisters,
I have a class that keeps userAuthInformatin with authuserid column,username column, and authusertoken column.
I want to insert uuid number in authusertoken column with every insert. I learned about Generated(GenerationTime.INSERT) but i don't know exact way to do this.
package entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
#Entity
#Table(name = "authusers")
public class AuthUser {
#Id
#GeneratedValue
private int authuserid;
#Generated(GenerationTime.INSERT)
#Column(name = "authusertoken")
private long authusertoken;
#Column(name = "username")
private String username;
public int getAuthuserid() {
return authuserid;
}
public void setAuthuserid(int authuserid) {
this.authuserid = authuserid;
}
public long getAuthusertoken() {
return authusertoken;
}
public void setAuthusertoken(long authusertoken) {
this.authusertoken = authusertoken;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
You can simply use the UUID Java class and assign a value to this token field at object creation time:
#Column(name = "authusertoken", columnDefinition = "BINARY(16)")
private UUID authusertoken = UUID.randomUUID();
If the current associated row already has a value, when fetching the Entity, Hibernate will set it using Reflection and override the authusertoken with the row's column value.
If this is a new object, the authusertoken will get a default UUID when the object is instantiated.
I am using Apache CXF (version 2.5.2) to implement REST services that do add, update, delete, query single and list of records on database tables. I am using OpenJPA (version 2.2.0) in the persistence layer.
As an example, I have a table named Complaint, mapped to an entity class of the same name. The complaint entity is similar to this:
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* The persistent class for the "Complaint" database table.
*
*/
#Entity
#Table(name = "Complaint")
#NamedQueries({
#NamedQuery(name = "cmplById", query = "SELECT c FROM Complaint c WHERE c.id = ?1"),
#NamedQuery(name = "cmplBySearchCriteria", query = "SELECT c FROM Complaint c WHERE c.status = ?1 AND c.category = ?2 AND c.location = ?3 AND "
+ "c.reportDate BETWEEN ?4 AND ?5 ORDER BY c.id DESC")
})
public class Complaint implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String description;
private String location;
private Timestamp reportDate;
private String reportedBy;
private String status;
private String category;
public Complaint() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "Description")
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
#Column(name = "Location")
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
#Column(name = "ReportDate", nullable = false)
public Timestamp getReportDate() {
return this.reportDate;
}
public void setReportDate(Timestamp reportDate) {
this.reportDate = reportDate;
}
#Column(name = "ReportedBy", nullable = false)
public String getReportedBy() {
return this.reportedBy;
}
public void setReportedBy(String reportedBy) {
this.reportedBy = reportedBy;
}
#Column(name = "Status", nullable = false)
public String getStatus() {
return this.status;
}
public void setStatus(String status) {
this.status = status;
}
#Column(name = "Category", nullable = false)
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
All the services are working fine, except for an issue related to two query operations which return data.
The operations getComplaintById and getAllComplaint currently return the entity(s) with all the fields that have values available in database, but with the id field returning as null. I tried structuring the queries in these methods in three ways - using named queries, using native sql and using the find method on entity manager - all of these give the same result.
Just to test, if I try to set the value of id field based on null checking, it is not allowing me, saying the id field has value which is final.
I found same result running the code against both MySQL and Postgres database. Here is code for one of the query method.
public Complaint getComplaintById(Integer id) {
Complaint cmpl;
//case-1: The NamedQueries as defined in the entity class Complaint --> #NamedQuery(name="cmplById", query="SELECT c FROM Complaint c WHERE c.id = ?1")
//Query query = em.createNamedQuery("cmplById");
//case-2: using NativeQuery
//Query query = em.createNativeQuery("SELECT * FROM COMPLAINT WHERE ID=?1", Complaint.class);
//query.setParameter(1, id);
//cmpl = (Complaint) query.getSingleResult();
//case-3: using find method on entity manager
cmpl = em.find(Complaint.class, id);
System.out.println("id = " + cmpl.getId());//returning null, but get method on all other fields returning their values as expected.
return cmpl;
}
In the log I see that the query statement which OpenJPA is executing includes all other fields but not the id field in the select clause as below.
1720 ramsjpa TRACE ["http-bio-8080"-exec-3] openjpa.jdbc.SQL - executing prepstmnt 14227847 SELECT t0.Category, t0.Description, t0.Location, t0.ReportDate, t0.ReportedBy, t0.Status FROM Complaint t0 WHERE t0.Id = ? [params=?]
I am working with CXF and OpenJPA for the first time, and I may be making a basic mistake, all help appreciated.
Are you possibly calling getComplaintById right after inserting it into your database? If so, then your issue might be related to using GenerationType.IDENTITY for your Id field. With GenerationType.IDENTITY, since the id is not assigned by the database until the row is inserted, the id cannot be obtained in the object until after commit or after a flush call. (Reference: http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Identity_sequencing)
So I think you'd need to do the following before trying to find your Complaint by Id:
Complaint complaint = new Complaint();
// Fill in your complaint fields...
em.persist(complaint);
em.flush();
em.getTransaction().commit();