hibernate Miscs

hibernate overview

JPA Vs Hibernate

The Java Persistence API, or JPA, is a specification that defines the management of relational data in a Java application. The API maps out a set of concepts that defines which objects within the application should be persisted, and how it should persist them.

JPA is only a specification and that it needs an implementation to work

With JPA specification defining how and what we should persist, we now need to choose an implementation provider to supply the necessary code.

Providers

  • hibernate
  • Eclipse Link

As you state JPA is just a specification, meaning there is no implementation. You can annotate your classes as much as you would like with JPA annotations, however without an implementation nothing will happen. Think of JPA as the guidelines that must be followed or an interface, while Hibernate's JPA implementation is code that meets the API as defined by the JPA specification and provides the under the hood functionality.

When you use Hibernate with JPA you are actually using the Hibernate JPA implementation. The benefit of this is that you can swap out Hibernate's implementation of JPA for another implementation of the JPA specification. When you use straight Hibernate you are locking into the implementation because other ORMs may use different methods/configurations and annotations, therefore you cannot just switch over to another ORM.

https://stackoverflow.com/questions/9881611/whats-the-difference-between-jpa-and-hibernate

https://www.baeldung.com/jpa-hibernate-difference

Hibernate.initialize

A LazyInitializationException will be thrown by Hibernate if an uninitialized collection or proxy is accessed outside of the scope of the Session, i.e., when the entity owning the collection or having the reference to the proxy is in the detached state.

Solution

  • use eager loading
  • do not close the session until job is done
  • fetch require data before session is closed

Sometimes a proxy or collection needs to be initialized before closing the Session. One way is to force initialization by calling entity.getXXX() or entity.getXXX().size(), for example. However, this can be confusing to readers of the code and it is not convenient for generic code.

Hibernate.initialize() and Hibernate.isInitialized(), provide the application with a convenient way of working with lazily initialized collections or proxies. Hibernate.initialize(entity.getXXX()) will force the initialization of a proxy, entity.getXXX(), as long as its Session is still open. Hibernate.initialize( ) has a similar effect for the collection of entities as well.

Without second level cache

due to the call to the Hibernate.initialize method, a secondary SQL query is executed to fetch the Post entity, and that’s not very efficient and can lead to N+1 query issues.

if you’re not using the second-level cache, it’s not a good idea to fetch lazy associations using secondary SQL queries either by traversing them or using the Hibernate.initialize method.

With second level cache

f you are using the second-level cache, it’s fine to use the Hibernate.initiaize to fetch extra associations that you need to fulfill your business use case. In this case, even if you have N+1 cache calls, each call should run very quickly since the second-level cache is configured properly and data is returned from the memory.

The Hibernate.initialize can be used for collections as well. Now, because second-level cache collections are read-through, meaning that they are stored in the cache the first time they get loaded when running the following test case:


date

1. util.date Vs sql.data

The *java.util.Date* class represents a particular moment in time, with millisecond precision since the 1st of January 1970 00:00:00 GMT (the epoch time).

With the introduction of Java 8, *java.time* package should be used. Prior to Java 8, an alternative solution was available – Joda Time.

The *java.sql.Date* extends *java.util.Date* class.

  • java.sql.Date corresponds to SQL DATE which means it stores years, months and days while hour, minute, second and millisecond are ignored. Additionally sql.Date isn't tied to timezones.
  • java.sql.Time corresponds to SQL TIME and as should be obvious, only contains information about hour, minutes, seconds and milliseconds.
  • java.sql.Timestamp corresponds to SQL TIMESTAMP which is exact date to the nanosecond (note that util.Date only supports milliseconds!) with customizable precision.

2. Hibernate DateTime

https://www.baeldung.com/hibernate-date-time

使用sql date (not a good choice)

private java.sql.Date sqlDate;

private java.sql.Time sqlTime;

private java.sql.Timestamp sqlTimestamp;

temporalValues.setSqlDate(java.sql.Date.valueOf("2017-11-15"));
temporalValues.setSqlTime(java.sql.Time.valueOf("15:30:14"));
temporalValues.setSqlTimestamp(
java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));

使用util date

util date不是直接map到 sql type, 使用@Temporal annotation(DATE, TIME or TIMESTAMP, ) 映射到对应的sql type

@Basic
@Temporal(TemporalType.DATE)
private java.util.Date utilDate;

@Basic
@Temporal(TemporalType.TIME)
private java.util.Date utilTime;

@Basic
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date utilTimestamp;

temporalValues.setUtilDate(
new SimpleDateFormat("yyyy-MM-dd").parse("2017-11-15"));
temporalValues.setUtilTime(
new SimpleDateFormat("HH:mm:ss").parse("15:30:14"));
temporalValues.setUtilTimestamp(
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
.parse("2017-11-15 15:30:14.332"));

java8 new DateTime API

Since Java 8, the new Java Date and Time API is available for dealing with temporal values. This API fixes many of the problems of java.util.Date and java.util.Calendar classes.

The types from the java.time package are directly mapped to corresponding SQL types. So there's no need to explicitly specify @Temporal annotation:

  • LocalDate is mapped to DATE
  • LocalTime and OffsetTime are mapped to TIME
  • Instant, LocalDateTime, OffsetDateTime and ZonedDateTime are mapped to TIMESTAMP
@Basic
private java.time.LocalDate localDate;

@Basic
private java.time.LocalTime localTime;

@Basic
private java.time.OffsetTime offsetTime;

@Basic
private java.time.Instant instant;

@Basic
private java.time.LocalDateTime localDateTime;

@Basic
private java.time.OffsetDateTime offsetDateTime;

@Basic
private java.time.ZonedDateTime zonedDateTime;

temporalValues.setLocalDate(LocalDate.parse("2017-11-15"));

temporalValues.setLocalTime(LocalTime.parse("15:30:18"));
temporalValues.setOffsetTime(OffsetTime.parse("08:22:12+01:00"));

temporalValues.setInstant(Instant.parse("2017-11-15T08:22:12Z"));
temporalValues.setLocalDateTime(
LocalDateTime.parse("2017-11-15T08:22:12"));
temporalValues.setOffsetDateTime(
OffsetDateTime.parse("2017-11-15T08:22:12+01:00"));
temporalValues.setZonedDateTime(
ZonedDateTime.parse("2017-11-15T08:22:12+01:00[Europe/Paris]"));

https://www.baeldung.com/java-util-date-sql-date

https://stackoverflow.com/questions/2305973/java-util-date-vs-java-sql-date


enum

https://docs.jboss.org/hibernate/orm/5.0/mappingGuide/en-US/html_single/#d5e678

可以使用AttributeConverter

@Entity
public class Person {
...
@Basic
@Convert( converter=GenderConverter.class )
public Gender gender;
}

public enum Gender {
MALE( 'M' ),
FEMALE( 'F' );

private final char code;

private Gender(char code) {
this.code = code;
}

public char getCode() {
return code;
}

public static Gender fromCode(char code) {
if ( code == 'M' || code == 'm' ) {
return MALE;
}
if ( code == 'F' || code == 'f' ) {
return FEMALE;
}
throw ...
}
}

@Converter
public class GenderConverter
implements AttributeConverter<Character,Gender> {

public Character convertToDatabaseColumn(Gender value) {
if ( value == null ) {
return null;
}

return value.getCode();
}

public Gender convertToEntityAttribute(Character value) {
if ( value == null ) {
return null;
}

return Gender.fromCode( value );
}
}

Hibernate Vs JDBC

JDBC

the JDBC template will probably be a bit faster, because it doesn't have the overhead that Hibernate has. But it will probably take much more time and lines of code to implement.

Hibernate

Hibernate has its learning curve, and you have to understand what happens behind the scenes, when to use projections instead of returning entities, etc. But if you master it, you'll gain much time and have cleaner and simpler code than with a JDBC-based solution.


Equality

https://howtodoinjava.com/hibernate/hibernate-entities-equality-and-identity/

Objects fetched from same session

Requesting a persistent object again from the same Hibernate session returns the same Java instance of a class, which means that you can compare the objects using the standard Java ‘==’ equality syntax.

Objects fetched from different sessions

if you request a persistent object from more than one Hibernate session, Hibernate will provide distinct instances from each session, and the == operator will return false if you compare these object instances.

Solution : overriding equal and hashcode

Hibernate wraps the actual object in a proxy so always use the getter methods inside instead of actual properties to compare.


OrphanRemoval

Orphan Removal – when a target entity in one-to-one or one-to-many relationship is removed from the relationship, it is often desirable to cascade the remove operation to the target entity.

Example: if an order has many line items and one of them is removed from the order, the removed line item is considered an orphan. If orphanRemoval is set to true, the line item entity will be deleted when the line item is removed from the order.

Orphan Removal Vs Cascade Delete

Cascade Delete removes all children when parent is removed. So, If you delete user entity, JPA deletes all his photos too.

Orphan Removal removes corresponding child when you remove it from the relationships. So, if you delete 1 photo from user.getPhotos() collection, JPA automatically removes that photo from database too.

Notes**: 可以code 里来handle 关联删除, 而非依赖于hibernate 自动做。


Batch

https://www.baeldung.com/jpa-hibernate-batch-insert-update

Hibernate doesn't enable batching by default. This means that it'll send a separate SQL statement for each insert/update operation:


@Transactional
@Test
public void whenNotConfigured_ThenSendsInsertsSeparately() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
entityManager.flush();
}


"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School1","1"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School2","2"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School3","3"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School4","4"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School5","5"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School6","6"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School7","7"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School8","8"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School9","9"]]
"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School10","10"]]

we should set *hibernate.jdbc.batch_size* property to a number bigger than 0.

Batch Insert Without Explicit Flush

@Transactional
@Test
public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
}

"batch":true, "querySize":1, "batchSize":5, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School1","1"],["School2","2"],["School3","3"],["School4","4"],["School5","5"]]
"batch":true, "querySize":1, "batchSize":5, "query":["insert into school (name, id) values (?, ?)"],
"params":[["School6","6"],["School7","7"],["School8","8"],["School9","9"],["School10","10"]]

Issue:

. When we persist an entity, Hibernate stores it in the persistence context. For example, if we persist 100,000 entities in one transaction, we'll end up having 100,000 entity instances in memory, possibly causing an OutOfMemoryException.

Batch Insert with Explicit Flush

First of all, the persistence context stores newly created entities and also the modified ones in memory. Hibernate sends these changes to the database when the transaction is synchronized. This generally happens at the end of a transaction. However, calling *EntityManager.flush()* also triggers a transaction synchronization.

Secondly, the persistence context serves as an entity cache, thus also referred to as the first level cache. To clear entities in the persistence context, we can call EntityManager.clear().

Transactional
@Test
public void whenFlushingAfterBatch_ThenClearsMemory() {
for (int i = 0; i < 10; i++) {
if (i > 0 && i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
School school = createSchool(i);
entityManager.persist(school);
}
}



Hibernate Validator

Bean validation API 2 (JSR-380) offers some popular annotations that can be attached to each bean property for the purpose of maintaining data integrity.

<!-- Java bean validation API - Spec -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>

<!-- Hibernate validator - Bean validation API Implementation -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.11.Final</version>
</dependency>


public class User {

@NotNull(message = "Please enter id")
private Long id;

@Size(max = 20, min = 3, message = "{user.name.invalid}")
@NotEmpty(message = "Please enter name")
private String name;

@Email(message = "{user.email.invalid}")
@NotEmpty(message = "Please enter email")
private String email;

public User(Long id, String name, String email) {
super();
this.id = id;
this.name = name;
this.email = email;
}

//Setters and Getters
}

// msg resources
// By default, all messages are resolved from ValidationMessages.properties file in classpath. I


// testing
public class TestHibernateValidator
{
public static void main(String[] args)
{
//Create ValidatorFactory which returns validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

//It validates bean instances
Validator validator = factory.getValidator();

User user = new User(null, "1", "abcgmail.com");

//Validate bean
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);

//Show errors
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
}
}