JPA mapping failing on strange message - mysql

I have a strange problem where the JPA mapping is failing on converting to a timestamp, but the value it's using appears to be the entire row, not just one variable.
The error is:
java.sql.SQLException: Value '1988├╗ ├╗├╗├╗├╗├╗├╗0
07 1234567 wk├╗0├╗├╗├╗├╗├╗ ' can not be represented as java.sql.Timestamp
where the value seems to be the entire row, with most of the bad characters being nulls. Debug logging isn't giving me much at the moment, and I'm not sure whether it's an error in my mapping class, collation issues, or something else.
MySQL workbench reads all the information from the table correctly. Running mysql from the command outputs all the data correctly. Neither show any special characters anywhere.
A simplified version of my mapping class is:
#Entity
#Audited
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class PersonSundry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="person_id", unique=true)
private Person person;
#ManyToOne(fetch=FetchType.EAGER)
private Lookup1 lookup1;
#ManyToOne(fetch=FetchType.EAGER)
private Lookup2 lookup2;
#Lob
private String backgroundInfo;
private LocalDate dateOfSomething1;
private LocalDate dateOfSomething2;
private LocalDate dateOfSomething3;
// getters and setters
}
Has anyone come across this before? Any ideas where else to look?
EDIT: The root cause turned out to be a generic failed cast of 00-00-0000 to a timestamp, however I'm going to leave the question open to see if someone knows where the strange error message was given instead of an exact one.

Are you using MYSQL?
Please try using this type of connection string.
jdbc:mysql://yourserver:3306/yourdatabase?zeroDateTimeBehavior=convertToNull
Datetimes with all-zero components (0000-00-00 ...) — These values can not be represented reliably in Java. Connector/J 3.0.x always converted them to NULL when being read from a ResultSet.
Connector/J 3.1 throws an exception by default when these values are encountered as this is the most correct behavior according to the JDBC and SQL standards. This behavior can be modified using the zeroDateTimeBehavior configuration property. The allowable values are:
exception (the default), which throws an SQLException with an SQLState of S1009.
convertToNull, which returns NULL instead of the date.
round, which rounds the date to the nearest closest value which is 0001-01-01.

Related

Is there any way to update SET type column using JPA query?

What I want to do is update field in table by JPA query.
I set my MySQL table with SET type.
create table staff (
id BIGINT,
...
roles SET('A', 'B', 'C') not null,
...
)
and class
public class Staff {
private Long id;
#Converter(someConverterHere.class)
private Set<Role> roles;
}
and I tried to update this field like the way below
#Modifying
#Query("update Staff s SET s.roles = :newRoles where s.id = :id")
Integer updateStaffRoles(#Param("id") Long id, #Param("newRoles") Set<Role> newRoles);
Then, When I gave this Set.of(Role.A, Role.B) as argument, it showed me the error like this,
Caused by: java.lang.IllegalArgumentException: Parameter value [A] did not match expected type [java.util.Set (n/a)]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54)
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27)
at org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90)
at org.hibernate.query.internal.QueryParameterBindingImpl.setBindValue(QueryParameterBindingImpl.java:55)
at org.hibernate.query.internal.QueryParameterBindingsImpl.expandListValuedParameters(QueryParameterBindingsImpl.java:636)
at org.hibernate.query.internal.AbstractProducedQuery.doExecuteUpdate(AbstractProducedQuery.java:1629)
at org.hibernate.query.internal.AbstractProducedQuery.executeUpdate(AbstractProducedQuery.java:1612)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$ModifyingExecution.doExecute(JpaQueryExecution.java:238)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor$QueryMethodInvoker.invoke(QueryExecutorMethodInterceptor.java:195)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:152)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 81 common frames omitted
It seems jpa query builder validates all single value in newRoles and compare its type with type of Staff.roles, which cannot be true (Role and Set)
Is there any way to solve this? I was not able to find any solution for this.
dependency
org.springframework.data:spring-data-jpa:2.3.2.RELEASE
=====================================================
Temporal solution:
I changed paramter type from Set to Set<Set> and it works. And now, This problem becomes problem when using QueryDsl. Using "set" of querydsl, this problem happens again :(
Querydsl
// newRoles : Set<Role>
// Error: IllegalArgumentException
query.set(QStaff.Staff.roles, newRoles));
// Of course, compile error
query.set(QStaff.Staff.roles, Set.of(newRoles)));
Custom types need to be mapped by a custom type in your JPA implementation. For Hibernate that is through the User Type interface. In order to use extended types through QueryDSL, you need to create custom expression types for it. Furthermore, for any of the operations you want to support (like set contains, equality), you will have to register custom functions. It can be done, but not using the regular API's. You're probably best off denormalizing your metamodel and using an #ElementCollection instead. However, if you insist on getting to work, here are some pointers:
hibernate-types is a widely popular project that implements many custom types for Hibernate. It doesn't implement SET, but you can probably use the implementation for arrays as basis.
hibernate-types-querydsl-apt is a project that extends metamodel generation of QueryDSL for some of the custom types. You should probably mimic anything in that repository for sets as well.

Timezone issue in Spring Boot JPA application

I am facing date time issue in my spring boot jpa application.
For example, In my database I have one column created_on which contains 2019-07-11 09:30:00 date.
When I fetch this record threw JPA it converts to UTC.
Means date 2019-07-11 09:30:00 converts to 2019-07-11 05:00:00.
My System time is in IST and date is saved in database in IST as well.
I am using mysql database.
In my Enitity
private Date createdOn;
Database column:
created_on timestamp
Service:
#Service
#Transactional(readOnly = true)
public class EntityTypeService {
#Autowired
private IEntityTypeRepository entityTypeRepository;
public EntityType findById(Long id) {
EntityType entityType = entityTypeRepository.findById(id).orElse(new EntityType());
System.out.println(entityType.getCreatedOn());
return entityType;
}
}
Repository
#Repository
public interface IEntityTypeRepository extends CrudRepository<EntityType, Long> {
}
Date in database is 2019-07-11 09:30:00
But when I print it on service System.out.println(entityType.getCreatedOn()); it gives me 2019-07-11 05:00:00.
This is generic issue in my whole application.
After so much research I found the solution.
Actually issue was because of
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
this property which I have set in my appication.properties.
When I removed this property everything works fine at the backend. Backend showing the perfect time which is available in database.
But now when response comes to frontend, at that time my date gets converted to UTC.
Like backend it's showing 2020-06-03 18:56:14.0 and when it comes to front end it converts to 2020-06-02T13:26:14.000+0000.
Which is also an issue for me.
So after some more research I found that Jackson by default converts all date to UTC when object send to frontend.
The solution to this problem is
spring.jackson.time-zone=IST
My Database and System timezone is IST so I have set IST to jackson timezone too which solves the problem.
Hope this answer may help someone.
You can set timezone for in database connection using serverTimezone eg:Asia/Kolkata and use useLegacyDatetimeCode=false
spring.datasource.url= = jdbc:mysql://127.0.0.1/db_name?useLegacyDatetimeCode=false&serverTimezone=Asia/Kolkata
And don't use legacy class Date rather use modern class LocalDateTime
When working on JPA + Spring Boot for a backend application, make sure to use same TimeZone through out server configuration. For example, to use UTC do as below:
On server OS level: sudo timedatectl set-timezone UTC (for example on Cent OS)
On MySQL / Database server in my.cnf file (optional)
Spring config: spring.jpa.properties.hibernate.jdbc.time_zone=UTC
MySQL URL query parameters spring.datasource.url=jdbc\:mysql\://localhost\:3306/my_db_name?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useLegacyDatetimeCode=false
And if you are using Jackson JSON framework you can automatically parse DateTime value to String format using #JsonFormat(shape= JsonFormat.Shape.STRING, pattern="dd MMM yyyy HH:mm:ss") on the entity property / field.
Application entry point #SpringBootApplication
#PostConstruct public void init(){ TimeZone.setDefault(TimeZone.getTimeZone("UTC")); }
This should cover most of the implementation challenges while work on Dates and Timestamps. Hope that it helps.
Try to avoid using Date for timestamps in time zones because it is not designed to handle these in a good way.
You could try to print the date object with a formatter or use LocalDateTime (or ZonedDateTime if time zones are important to you) instead.
You may try putting #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX") on createdOn setter.

Envers + MYSQL + List<String> = SQLSyntaxErrorException: Specified key was too long;

I am extending an existing application with Audit support using Envers. I annotated all #Entity classes and I got a bunch of Exception traces. When taking a look at them, it seems that they all relate to attribute definitions that have the following form
protected List<String> testActivities;
#ElementCollection
protected List<String> getTestActivities() {
return testActivities;
}
public void setTestActivities(List<String> testActivities) {
this.testActivities = FXCollections.observableList(testActivities);
}
All exceptions are List<String> attributes, and the getter method has a #ElementCollection annotation.
The Exception I am getting is always (here is the exception for the above testActivities attribute)
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table TestCase_testActivities_AUD (REV integer not null, TestCase_id bigint not null, testActivities varchar(255) not null, REVTYPE tinyint, primary key (REV, TestCase_id, testActivities)) engine=MyISAM" via JDBC Statement
..
Caused by: java.sql.SQLSyntaxErrorException: Specified key was too long; max key length is 1000 bytes
..
I guess the issue is the primary key containing the testActivities?!
The testActivities attribute refers to a list of instructions that a user has to perform, so reducing the String length on the code side, as suggested on some StackOverflow pages related to the key length issue, is probably not an option?!
Currently all tables are created with DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci and I could probably save memory by using utf8 instead of utf8mb4, but is that a good and reliable solution?
How to fix this the right way? I am open to different views regarding the above two points.
I run MySQL Server 8.0.15, MyISAM, and
I am using Spring Boot which gives me Hibernate Envers 5.3.10
I forgot to mention that I am using #Access(AccessType.PROPERTY)on the class level. Anyway, I extended the related getter methods
#ElementCollection
#Column(length=175) // keep in sync with maxDBStringLength
public List<String> getEnvironmentalInterfaces() {
return environmentalInterfaces;
}
Thus actually does the trick. However, in order to not loose information, I also extended all methods to add an element to the list, like so
// Must be in sync with #Column(length=175) definitions
protected static int maxDBStringLength = Constants.maxDBStringLength;
public void addEnvironmentalInterfaces(String environmentalInterface) throws StringTooLongException {
if(environmentalInterface.length() > maxDBStringLength) {
throw new StringTooLongException(maxDBStringLength, environmentalInterface.length());
}
environmentalInterfaces.add(environmentalInterface);
}
Now all tables are created. Unfortunately I have now a NullPointer issue, which you find here Envers NullPointerException when creating test data - just in case you are going through the same learning curve.

EclipseLink: "Updates are not allowed" on a native query INSERT INTO

since upgrading to EclipseLink 2.5 from EL 2.1 we get a PersistenceException on a previously running code.
The scenary is really simple: we have two identical tables, the only difference is that one is a "history" version of the other. Basically, when we are sure that a row won't change anymore, we move the row to the history table. This means that while the id is generated on the first table, it is "inherited" on the history table. Here are the entities:
#Entity
#Table(name = "DOCUMENT")
public class Document implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
// Some other fields
}
#Entity
#Table(name = "DOCUMENT_HISTORY")
public class DocumentHistory implements Serializable {
#Id
private long id;
// Some other fields
}
In order to move a row to the history table we use a native query (because some columns are not mapped as fields in the entity classes):
String query = "INSERT INTO DOCUMENT_HISTORY SELECT * FROM DOCUMENT t WHERE t.id=?1";
Query updateQuery = entityManager.createNativeQuery(query);
updateQuery.setParameter(1, document.getId());
updateQuery.executeUpdate();
When executing this query, sometimes this exception is thrown:
javax.persistence.PersistenceException: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5):
org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:868)
at org.eclipse.persistence.internal.jpa.QueryImpl.performPreQueryFlush(QueryImpl.java:963)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeUpdate(QueryImpl.java:296)
at org.myc.utility.jpa.user.DocumentManager.toDocumentHistory(DocumentManager.java:141)
at java.lang.Thread.run(Thread.java:662)
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
at org.eclipse.persistence.exceptions.ValidationException.primaryKeyUpdateDisallowed(ValidationException.java:2548)
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.writeFromObjectIntoRowWithChangeRecord(AbstractDirectMapping.java:1257)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildRowForUpdateWithChangeSet(ObjectBuilder.java:1768)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1030)
at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:899)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:798)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1793)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1775)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1726)
at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:267)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:192)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:138)
at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4196)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1587)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:452)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:863)
... 5 more
Did anyone have the same problem? Why do you think this doesn't happen on all rows but only some of them (with no apparent specific characteristics)?
Thanks in advance,
Luc
P.S.: Is it correct for EL to perform mapping/query validation on a native query?
As stated in JPA 2.0 specification, executeUpdate() executes an update or delete statement thus insert is not supported.
You can insert an entity into the underlying database by persisting and flushing it with use of EntityManager (within a transaction). An dummy example in your particular case might look as follows:
// transaction starts
Document doc = em.find(Document.class, id);
DocumentHistory docHistory = new DocumentHistory(doc);
em.persist(docHistory);
// transaction ends
NOTE: instead of a proprietary solution (which is fine) you may consider of using EclipseLink's auditing feature:
EclipseLink also support full history support, which allows a complete history of all changes made to the database to be tracked in a mirror history table.
Ok, I found the problem and it wasn't related at all with that query, so I still don't know why the exception is thrown there.
The problem was that instead of using entityManager.find() to get the newly inserted history row, we were creating a DocumentHistory instance (with same ID) which was not attached to a persistence context, then using that instance to perform other operations.
Hope it helps someone.

Setting column length of a Long value with JPA annotations

I'm performing a little database optimisation at the moment and would like to set the column lengths in my table through JPA. So far I have no problem setting the String (varchar) lengths using JPA as follows:
#Column(unique=true, nullable=false, length=99)
public String getEmail() {
return email;
}
However, when I want to do the same for a column which is of type Long (bigint), it doesn't work. For example, if I write:
#Id
#Column(length=7)
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
The column size is still set as the default of 20. Are we able to set these lengths in JPA or am I barking up the wrong tree?
Thanks,
Gearoid.
precision, scale make more sense for a numeric type. Also depends whether the JDBC driver and RDBMS allows setting of those on the particular column type
Are you using Eclipse as your IDE? If so, I suggest you make use of the Dali plugin (already installed) and active the "JPA Details" view. This view will help guide you as to what attributes you need to plug into your annotations.
You could have scale=7 instead of length=7. However JPA is still going to tell the database to prepare to hold an int of scale 20.
Also make sure you have your database dialect properly set.