-
Notifications
You must be signed in to change notification settings - Fork 3
Home
The project DbAssist provides a clean and simple way to solve the well-known issue with JVM adding improper time shift to dates, when saving to or reading from a database. The issue does not occur at all on condition that all the components we are using in the project (servers, clients etc.) are set up in the same time zone. If at least one component has a different time zone (for example, we want to store dates in a database as UTC0, but leave the application servers in local time zone), then we experience unexpected time shift whenever we perform read/write operation. The solution differs depending on whether we use JPA Annotations or Hibernate Mapping files (.hbm) to define entities and their relations in the database.
The provided unit testing should be also treated as an example of how to use the classes provided with the DbAssist library. In order to run the unit tests you need to set up a database, so that the Liquibase plugin used in DbAssist-commons project could create necessary schemas and tables. More details about how to install the project properly are available on the wiki Installation page.
In order to fix the issue with date shift, you need to determine first if you want to use JPA annotations or .hbm files to map your entities. Depending on your choice, add the following dependency to your project's pom file and pick the correct version from the table below.
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-5.2.2</artifactId>
<version>1.0-RELEASE</version>
</dependency>
The supported Hibernate versions and their fix counterparts:
Hibernate version | HBM | JPA |
---|---|---|
3.3.2.GA | DbAssist-hbm-3.3.2 |
N/A |
3.6.10.Final | DbAssist-hbm-3.6.10 |
N/A |
4.2.21.Final | DbAssist-4.2.21 |
DbAssist-4.2.21 |
4.3.11.Final | DbAssist-4.3.11 |
DbAssist-4.3.11 |
5.0.10.Final | DbAssist-5.0.10 |
DbAssist-5.0.10 |
5.1.1.Final | DbAssist-5.1.1 |
DbAssist-5.1.1 |
5.2.2.Final | DbAssist-5.2.2 |
DbAssist-5.2.2 |
The fix is slightly different for both entity mapping methods:
You do not modify the java.util.Date
type of dates fields in your entity class. However, you need to change the way how they are mapped in the .hbm
file of your entities. You can do it by using our custom type, UtcDateType
:
ExampleEntity.hbm.xml
<property name="createdAt" type="com.montrosesoftware.dbassist.types.UtcDateType" column="created_at"/>
ExampleEntity.java
(not modified)
public class ExampleEntity {
private int id;
private String name;
private Date createdAt;
//setters and getters
}
In case of JPA Annotations with Spring Boot, just add the @EnableAutoConfiguration
annotation before the application class. However, if you are using just plain Hibernate (with persistence.xml
configuration file), add the following property into the configuration. It will include the necessary metadata defined at the package level (e.g. package-info.java
).
persistence.xml
...
<persistence-unit ...>
...
<class>com.montrosesoftware.dbassist.types</class>
...
</persistence-unit>
...
The exception is when we are using Hibernate's Specification
class to specify WHERE
conditions. In order to fix it we have two options:
The following code shows how to use Specification
to add WHERE
conditions with use of named parameters. The commented out part shows the standard way to use it. As mentioned before, the standard way would fail and add improper time shift to utcDate
:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Root<User> userRoot = criteriaQuery.from(User.class);
//Specification<User> specs = (root, query, cb) ->
// cb.equal(root.get("createdAt"), utcDate);
String paramName = "pn";
Specification<User> specs = (root, query, cb) ->
cb.equal(root.get("createdAt"), cb.parameter(Date.class, paramName));
Predicate predicate = specs.toPredicate(userRoot, criteriaQuery, criteriaBuilder);
criteriaQuery.where(predicate);
//TypedQuery<User> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setParameter(paramName, utcDate);
List<User> results = typedQuery.getResultList();
Unfortunately, using such a fix makes the code less readable and more difficult to maintain. Therefore we recommend using the next option.
First, install the library by adding the following dependency into your .pom file:
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-jpa-commons</artifactId>
<version>1.1-RELEASE</version>
</dependency>
The following code snippet shows how DbAssist-jpa-commons
allows us to create complex conditions and fix date shift issue in an easy way:
ConditionsBuilder cb = new ConditionsBuilder();
Condition conA = cb.equal("createdAt", utcDateA);
Condition conB = cb.equal("createdAt", utcDateB);
cb.apply(or(conA, conB));
List<User> results = uRepo.find(cb);
More information how to use ConditionsBuilder
and other classes from the library is presented in the links below.
Email: [email protected]