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

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.

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.

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.

JPA mapping failing on strange message

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.

Automatically generated Entities from mysql database in Netbeans always fail to deploy

I am new to Java EE (and to Netbeans). I have am trying to automatically generate entity classes from my mysql database... For simple relationships it works, but for the following it always fails:
i get the following error:
Internal Exception: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The #JoinColumns on the annotated element [field tblExpandituresTranx] from the entity class [class entities.restaurant.TblContents] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.. Please see server.log for more details.
I think... I have some error in my database or perhaps EclipseLink JPA tool is kaput!
please help!
Could be that your schema is upside down.
Or you could actually read the exception you're getting and figure out what it's telling you:
The #JoinColumns on the annotated element [field tblExpandituresTranx] from the entity class [class entities.restaurant.TblContents] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn
Looks like you've got an incomplete specification for the JOIN.
I solved the problem myself...
Apparently JPA has a problem with multiple primary keys in bridge tables. So, instead of having foreign keys as primaries I just converted them to unique indexed and everything worked just fine!! wuhu!!

Turning IDENTITY_INSERT ON on a table to load it with DB Unit

I try to load a table, that have an identity column, with DB Unit. I want to be able to set the id value myself (I don't want the database generate it for me).
Here is a minimal definition of my table
create table X (
id numeric(10,0) IDENTITY PRIMARY KEY NOT NULL
)
To insert a line in X, I execute the following SQL
set INDENTITY_INSERT X ON
insert into X(id) VALUES(666)
No problem. But when I try to load this table with the following db unit XML dataset (RS_7_10_minimal_ini.xml)
<dataset>
<X id="666"/>
</dataset>
using the following minimal JUnit (DBTestCase) test case :
package lms.lp.functionnal_config;
import java.io.FileInputStream;
import org.dbunit.DBTestCase;
import org.dbunit.PropertiesBasedJdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import lms.DBUnitConfig;
import org.junit.Test;
public class SampleTest extends DBTestCase
{
public SampleTest(String name)
{
super( name );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, DBUnitConfig.DBUNIT_DRIVER_CLASS );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, DBUnitConfig.DBUNIT_CONNECTION_URL );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, DBUnitConfig.DBUNIT_USERNAME );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, DBUnitConfig.DBUNIT_PASSWORD );
}
protected IDataSet getDataSet() throws Exception
{
return new FlatXmlDataSetBuilder().build(new FileInputStream("src/test/resources/RS_7_10_minimal_ini.xml"));
}
#Test
public void testXXX() {
// ...
}
}
It fails with the following exception
com.sybase.jdbc3.jdbc.SybSQLException: Explicit value specified for identity field in table 'X' when 'SET IDENTITY_INSERT' is OFF.
It seems DB Unit does not turn identity ON before inserting a row for which the value of the identity column is specified.
I already tried to execute myself on the connection retrieved from the JdbcDataBaseTester but no luck. Probably a new connection or not the same connection used to push the data into de DB.
Any idea?
Thanks a lot for your help all !
Octave
Yes, found the solution in the DBUnit FAQ actually
Can I use DbUnit with IDENTITY or auto-increment columns?
Many RDBMSes allow IDENTITY and auto-increment columns to be implicitly overwritten with client values. DbUnit can be used with these RDBMS natively. Some databases, like MS SQL Server and Sybase, need to explicitly activate client values writing. The way to activate this feature is vendor-specific.
DbUnit provides this functionality for MS SQL Server with the InsertIdentityOperation class.
Although it is written for the MS SQL Server, is also works for Sybase. So I push my data set to db with
new InsertIndentityOperation(DatabaseOperation.CLEAN_INSERT).execute(connection,initialDataSet);
Et voilà.
Thanks for your answer rawheiser.
Not familar enough with DBUnit to help you with the specifics; but I have used a table truncate and reseeding the identity value in similar situations.
dbcc checkident