Sequence not available when using Spring and Testcontainers - postgresql

I am trying to use postgres in a testcontainer for integration tests and it throws an error during JUnit execution due to a missing sequence.
I've tried not specifying the generator but it fails with hiberate_sequence not found. This works (named generators) in production so I'm pretty sure it is not a simple syntax issue.
Any suggestions on what could be causing the error?
Update
I created a new test without Testcontainers that uses an existing database with the table and generator already in the schema. That test is green. The SQL used to create the table and generator is the Spring generated SQL.
Environment: Spring Boot v2.7.8, postgres 12, testcontainers 1.17.6, JUnit 5, Java 11
Trivial example
#Builder(toBuilder = true)
#Entity
#Table(name = "motor")
public class MotorImpl implements Motor, Serializable {
private static final long serialVersionUID = -8719527647178838271L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "motor_generator")
#SequenceGenerator(name = "motor_generator", sequenceName = "motor_id_seq", allocationSize = 1)
private Long id;
#Version
private Integer version;
private String name;
// getters, setters, constructors generated by lombok
}
JUnit5 test class
#SpringBootTest
#Testcontainers
class MotorRepositoryTest {
#Container
private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:12");
#DynamicPropertySource
static void registerPostgresProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
#Autowired
MotorRepository repo;
#Test
#Transactional
void givenValidMotor_whenInsert_thenRecordCreated() {
MotorImpl m = MotorImpl.builder().build();
MotorImpl m_saved = repo.save(m);
assertNotNull(m_saved.getId());
Optional<MotorImpl> m_retrieved = repo.findById(m_saved.getId());
assertTrue(m_retrieved.isPresent());
}
}
captured auto-DDL from Spring
create sequence motor_id_seq start 1 increment 1;
create table motor (
id int8 not null,
name varchar(255),
status int4,
version int4,
primary key (id)
);
console log
14:31:24.410 [main] INFO org.springframework.test.context.transaction.TransactionContext - Began transaction (1) for test context [DefaultTestContext#2681185e testClass = MotorRepositoryTest, testInstance = com.vogelware.testcontainer.motor.impl.MotorRepositoryTest#41f5389f, testMethod = givenValidMotor_whenInsert_thenRecordCreated#MotorRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration#3a012678 testClass = MotorRepositoryTest, locations = '{}', classes = '{class com.vogelware.testcontainer.TestcontainerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#2c4d1ac, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#41e68d87, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#24ba9639, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer#4331d187, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#335b5620, org.springframework.test.context.support.DynamicPropertiesContextCustomizer#513b5eb, org.springframework.boot.test.context.SpringBootTestArgs#1, org.springframework.boot.test.context.SpringBootTestWebEnvironment#3c153a1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager#69cb134]; rollback [true]
14:31:24.562 [main] DEBUG org.hibernate.SQL -
select
nextval ('motor_id_seq')
14:31:24.573 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
14:31:24.573 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ERROR: relation "motor_id_seq" does not exist
Position: 17

Related

Hibernate ignores the schema name while calling nextval() - from postgres 12

My pojo definition worked fine till I used Postgres 9.x, When I changed the database to postgres 12 I get Sequence not found error when inserting a new row to a table. When I debugged the sql statements run in the database I found that hibernate ignored the schema name.
Postgres 9 : select nextval('ita.ita_settings_is_is_id_seq')....
Postgres 12: select nextval('ita_settings_is_is_id_seq')....
My Pojo definition is as follows
#Entity
#Table(name = "ita.ita_settings_is")
#SequenceGenerator(name = "settingItaIdSeq", sequenceName = "ita.ita_settings_is_is_id_seq", initialValue = 1, allocationSize = 1)
public class itaSettings implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "settingitaIdSeq")
#Column(name = "is_id")
private int id;

JPA entity sequence generating

In spring boot JPA I tried to implement sequence generator but it is not working.
the following is my entity
#Entity
#Table(name = "role_level")
public class RoleLevel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "role_level_sequence", sequenceName = "role_level_id_seq",allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "role_level_sequence")
#Column(name = "id", updatable = false)
private Long id;
#Column(name = "role_level")
private String roleLevel;
#Column(name = "role_level_description")
private String roleLevelDescription;
//getters and setters
}
when I insert value in directly through the database then next sequence from the db is not getting in jpa.it shows
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "role_level_pkey"
Detail: Key (id)=(7) already exists.
But the console shows
Hibernate: select nextval ('role_level_id_seq')
I think its not working.
Is there any solution for this.?

Duplicate keys with hibernate

I have a Postgres 9.5.5 table named mytable under schema myschema. It does not have a primary key - we missed adding it when the table was created. This is causing rows with duplicate values in id column of table. The only access points to this table are through the save(), update() and delete() methods of the Hibernate 4.3.10 Final entity class. Nobody is manually updating the database far as I know. What part of the code is sending duplicate id column values to the table? The entity class looks like this -
#Entity
#Table(name = "mytable", schema = "myschema")
public class MyTable implements Serializable {
/** Id. */
#Id
#GeneratedValue(generator = "myschema.mytable_seq", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "myschema.mytable_seq", sequenceName = "myschema.mytable_seq")
#Column(name = "id", unique = true, nullable = false)
private int id;
Below is the sequence definition in postgres -
CREATE SEQUENCE myschema.mytable_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 765
CACHE 1;
The hibernate code is something like this (sessionFactory is an autowired instance of org.hibernate.SessionFactory -
#Repository
public class HBMyTableDao extends HBAbstractDAO<MyTable> implements MyTableDao {
public void save(MyTable model) {
sessionFactory.getCurrentSession().save(model);
}
public void saveOrUpdate(MyTable model) {
sessionFactory.getCurrentSession().saveOrUpdate(model);
}
public void update(MyTable model) {
sessionFactory.getCurrentSession().update(model);
}
public void delete(MyTable model) {
sessionFactory.getCurrentSession().delete(model);
}
}
Hibernate requires primary key.
see https://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#mapping-declaration-id
Maybe you can add a autogenerated column to the table

jpa, eclips-link 2.5.1: OneToMany not working on columns not primary key

I have these two entities:
Anagrafica
#Entity
#Access(AccessType.FIELD)
#Table(name = "S_MC_CC_USER")
#SequenceGenerator(name = "SEQ_ID", sequenceName = "SEQ_ID", allocationSize = 1)
public class Anagrafica implements Serializable{
private static final long serialVersionUID = 332466838544720886L;
#EmbeddedId
private AnagraficaId anagraficaId;
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ID")
private Long userId;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID", updatable = false, insertable = false)
private List<Mobile> mobiles;
/**
* La classe di dominio che modella la chiave primaria di un {#link Anagrafica}
*
* #author Massimo Ugues
*
*/
#Embeddable
static public class AnagraficaId implements Serializable {
private static final long serialVersionUID = -54640203292300521L;
#Column(name = "ANAG_UTENTE")
private String bt;
#Column(name = "COD_ABI")
private String abi;
public AnagraficaId() {
super();
}
Mobile
#Entity
#Table(name = "S_MOBILE")
#SequenceGenerator(name = "SEQ_MOBILE", sequenceName = "SEQ_MOBILE", allocationSize = 1)
public class Mobile implements Serializable{
private static final long serialVersionUID = 5999493664911497370L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_MOBILE_DEVICE_REGISTRY")
#Column(name = "ID_MOBILE")
private Long mobileId;
#Column(name = "DEVICE_TOKEN")
private String deviceToken;
#Column(name = "DATA_INSERIMENTO")
#Temporal(TemporalType.TIMESTAMP)
private Calendar dataInserimento = Calendar.getInstance();
With eclispe-link 2.1.2 all works great, but with eclispe-link 2.5.1 I got this exception:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Predeployment of PersistenceUnit [persistence-unit] failed.
Internal Exception: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The #JoinColumns on the annotated element [field mobiles] from the entity class [class com.intesasanpaolo.domain.entities.sub.Anagrafica] 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.
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.createPredeployFailedPersistenceException(EntityManagerSetupImpl.java:1954)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:1945)
at org.eclipse.persistence.jpa.PersistenceProvider.createContainerEntityManagerFactory(PersistenceProvider.java:322)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:288)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 40 more
The problem is the OneToMany association based on a foreign key that is not primary key.
Since I cannot change the database model how can I make it work?
Kind regards
Massimo
The reason it worked in a prior version was that it EclipseLink doesn't look at the fields in the mapping, but with JPA adding derived Id support, EclipseLink now validates the number of foreign keys match the number of ID fields.
James' answer here
JPA #JoinColumn issues while joining on non primary key columns
explains it that you'll need to use a descriptorCustomizer to change the JPA mapping. So you would either not map the field in JPA (mark it as #Transient) and then add a mapping in the customizer, or have the JPA mapping to use all primary key fields and then change the mapping in the customizer to only use the USER_ID->USER_ID fields.
EclipseLink customizers are shown here:
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_customizer.htm
Ok, this is the Customizer I created:
public void customize(ClassDescriptor descriptor) throws Exception {
// handle the oneToManyMapping to non foreign keys
ManyToManyMapping mapping = (ManyToManyMapping) descriptor.getMappingForAttributeName("mobileDevices");
ExpressionBuilder builder = new ExpressionBuilder();
mapping.setSelectionCriteria(builder.getField("USER_ID").equal(builder.getParameter("USER_ID")));
// handle the insert statement
mapping.setInsertCall(new SQLCall(""));
}
As suggested from Chris this works great with the selection.
I had to modify the Insert Call since eclipse-link tried to create and insert statement on a mapping table that I haven't.
The problem now is on the delete: when I try to delete the collection from the source association (i.e. Cliente) as described here
Cliente.ClienteId id = new Cliente.ClienteId(abi, bt);
Cliente cliente = clienteRepository.findOne(id);
cliente.setMobileDevices(null);
I need eclipse link to delete the orphan.
The dml generated is the following:
DELETE FROM S_MC_CC_CLIENTI_S_MOBILE_DEVICE_REGISTRY WHERE ((mobileDevices_ID_MOBILE_DEVICE_REGISTRY = 13) AND ((ANAG_UTENTE = '71576493') AND (COD_ABI = '01025')))
Since I haven't the mapping table I modified the customizer adding a setDeleteCall statement :
mapping.setDeleteCall(new SQLCall("DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE USER_ID = #USER_ID"));
In this way eclipse link generates 2 dml:
DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE USER_ID = NULL
DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE (ID_MOBILE_DEVICE_REGISTRY = 13)
The first is the translation of my SQLCall, but without the correct parameter: any idea how to generate only the correct delete statement?
Kind regards.
Massimo

Using #OneToOne with Cascade.DELETE in embedded type

In an application I use EclipseLink 2.4.1 with Java Persistence 2.0.4.
I have a OneToOne mapping in an embedded class. Everything works fine, except deleting. When I try to delete the object containing the embedded class, the following exception occurs. I checked and I am not calling remove on the embedded object by myself somewhere in the code. Does anybody knows how to avoid this error or how to get around it?
Exception [EclipseLink-6002] (Eclipse Persistence Services - 2.4.1.v20121003-ad44345): org.eclipse.persistence.exceptions.QueryException
Exception Description: Aggregated objects cannot be written/deleted/queried independently from their owners.
Descriptor: [RelationalDescriptor(org.openlca.web.model.ProcessModelInfo --> [])]
Query: DeleteObjectQuery(org.openlca.web.model.ProcessModelInfo#77cc2975)
at org.eclipse.persistence.exceptions.QueryException.aggregateObjectCannotBeDeletedOrWritten(QueryException.java:240)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.prepare(ObjectLevelModifyQuery.java:205)
at org.eclipse.persistence.queries.DeleteObjectQuery.prepare(DeleteObjectQuery.java:327)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:614)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:575)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:820)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:751)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.DeleteObjectQuery.executeInUnitOfWorkObjectLevelModifyQuery(DeleteObjectQuery.java:119)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2875)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1602)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1584)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1535)
at org.eclipse.persistence.queries.DeleteObjectQuery.executeDatabaseQuery(DeleteObjectQuery.java:194)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:852)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:751)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.DeleteObjectQuery.executeInUnitOfWorkObjectLevelModifyQuery(DeleteObjectQuery.java:119)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2875)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1602)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1584)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1535)
at org.eclipse.persistence.internal.sessions.CommitManager.deleteAllObjects(CommitManager.java:334)
at org.eclipse.persistence.internal.sessions.CommitManager.deleteAllObjects(CommitManager.java:288)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1422)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:634)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1509)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:266)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1147)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:84)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:63)
at org.project.ProcessDao.delete(ProcessDao.java:41)
The relevant class snippets look like this (Process and LongText are added in the persistence.xml) - The error occurs when trying to delete a process:
Entity Class Process
#Entity
public class Process {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "process_seq")
#Column(name = "id")
private long id;
....
#Embedded
private ProcessModelInfo modelInfo = new ProcessModelInfo();
....
}
Embedded Class ProcessModelInfo
#Embeddable
public class ProcessModelInfo {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name= "f_modelling_constants")
private LongText modellingConstants = new LongText();
...
}
Entity Class LongText
#Entity
#Table(name = "tbl_long_texts")
public class LongText {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "long_text_seq")
#Column(name = "id")
private long id;
#Lob
#Column(name = "text")
private String text;
....
}
The ProcessDao.delete method looks like this:
#Override
public void delete(Process entity) throws Exception {
if (entity == null)
return;
EntityManager em = createManager();
try {
em.getTransaction().begin();
em.remove(em.merge(entity));
em.getTransaction().commit();
} finally {
em.close();
}
}
I can't see how this would occur, but if you can create a reproducible test case, please log a bug.
Check that you don't have any events that may be call remove on the embeddable.
Try debugging or set logging level to finest.
You may want to try the 2.5 release, as it may have been fixed (although I don't see any changes in the code).