Spring Boot - mapping Entities from external jar - java

I am having some trouble with Spring Boot, Spring Data and having Entities in an external jar. Any help would be greatly appreciated!
My Sprint Data repository looks like this:
#Repository
public interface MyFileRepository extends PagingAndSortingRepository<MyFile, Long> {
#Modifying
#Transactional
#Query("Delete from MyFile f where f.created < ?1")
long deleteOldEntities(Date cutoffDate);
}
My entity, which is in another jar entirely looks like this:
#Entity
#SequenceGenerator(
name = "SequenceIdGenerator",
sequenceName = "SEQ_ID_MY_FILE",
allocationSize = 20
)
#Table(
name = "MYFILE_TABLE"
)
public class MyFile extends BaseEntity {
private long id;
private byte[] data;
[...]
public MyFile() {}
#Id
#Column(
name = "id",
nullable = false
)
#GeneratedValue(
generator = "SequenceIdGenerator"
)
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
[...]
}
And the BaseEntity looks like this:
#MappedSuperclass
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
private static final Charset UTF_8 = Charset.forName("UTF-8");
private Date created = null;
private Date updated = null;
public BaseEntity() {}
#Column(
name = "created"
)
#Temporal(TemporalType.TIMESTAMP)
public Date getCreated() {
return this.created == null?null:new Date(this.created.getTime());
}
public void setCreated(Date created) {
if(created != null) {
this.created = new Date(created.getTime());
}
}
So, when I try to run this code I get a long stacktrace which basically ends with:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: MyFile is not mapped [Delete from MyFile f where f.created < ?1]
I believe that this may have something to do with the Spring Boot Configuration. The external jar does not have and #SpringBootApplication anywhere. It is basically just a jar with all my Entities.
My application jar however has this:
#SpringBootApplication
#EntityScan("myapp.service.dao.entity") --> This is the package where all my entities are located.
public class CommonApplication {
}
What is my error?

To scan entities residing in jar, you have to set packagesToScan field of LocalSessionFactory.
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean localSessionFactory = new LocalSessionFactoryBean();
localSessionFactory.setDataSource(dataSource);
localSessionFactory
.setPackagesToScan(new String[]{"myapp.service.dao.entity", "com.application.entity"});
return localSessionFactory;
}

I got this working using by using the following bean to set the packages scan:
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setShowSql(false);
vendorAdapter.setDatabase(Database.MYSQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("add packages here");
return factory.getObject();
}

Related

Hibernate is not using the proxy of a session scope bean spring

I'm using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}

Use additional JNDI with Spring Boot

I have an enterprise application set to work with a JNDI. Here's the configuration in my application.properties.
spring.datasource.jndi-name=jdbc/MYPRIMARYDATABASE
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.properties.hibernate.current_session_context_class=jta
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.properties.hibernate.transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
spring.jackson.serialization-inclusion=NON_NULL
and my Spring main: Application.java
#SpringBootApplication
#EnableScheduling
#EnableJpaRepositories(basePackages = { "it.mypackage.data.access.database" })
#EntityScan(basePackages = { "it.mypackage.data.access.model" })
#ComponentScan(basePackages = { "it.mypackage" })
public class Application extends SpringBootServletInitializer implements WebApplicationInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
#Bean
public SessionFactory sessionFactory(#Qualifier("entityManagerFactory") EntityManagerFactory emf) {
return emf.unwrap(SessionFactory.class);
}
}
Inside the package it.mypackage.data.access.database there is a DAO interface and a DAO facade which perform operations on the datasource and in the it.mypackage.data.access.model package where all table entities are declared.
DatabaseDAOFacade.java
#Repository
#Transactional
public class DatabaseDAOFacade implements DatabaseDAOInterface {
private final SessionFactory sf;
#SuppressWarnings("unused")
private EntityManager entityManager;
protected Session getSession() {
return sf.getCurrentSession();
}
#Autowired
public DatabaseDAOFacade(SessionFactory sf, EntityManager entityManager) {
this.sf = sf;
this.entityManager = entityManager;
}
#SuppressWarnings("unchecked")
public <T> T save(T o) {
return (T) this.getSession().save(o);
}
.
.
}
Here I have a new entity which should use an additional JNDI
Photo.java
#Entity
#Table(name = "T_PHOTO")
public class Photo {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "PHOTO_REQUEST_SEQ_GEN")
#SequenceGenerator(name = "PHOTO_REQUEST_SEQ_GEN", sequenceName = "PHOTO_REQUESTS")
private Long id;
#Column(name = "USERID")
private String userId;
#Column(name = "UPLOAD_DATE")
private Date uploadDate;
.
.
.
}
Is there a clean way to add a second JNDI and set Photo.java to use the new one?

Hibernate: generated Id remains in a not persistent entity after SQLIntegrityConstraintViolationException

Hello, everybody!
Some time ago I run into a trouble: if save method of repository fails, identifier, injected to a bean by Hibernate, remains in the bean. That behaviour may led us to a situation, when we will think about our not persistent bean as about persistent one. I would be pleased to know what practice is common to avoid this situation.
Example test(spring boot + hibernate + oracle database):
#Entity
#SequenceGenerator(name = "TEST_ENTITY_GENERATOR", allocationSize = 1, sequenceName = "TEST_ENTITY_SEQ")
public class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_ENTITY_GENERATOR")
private Long id;
#Column(nullable = false)
private String name;
public Long getId() {
return id;
}
}
#Repository
public interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class RemainingIdTest {
#Autowired
private TestEntityRepository testEntityRepository;
#Test
public void test() {
TestEntity entity = new TestEntity();
try {
Assertions.assertThat(entity.getId()).isNull();
testEntityRepository.save(entity);
Assertions.fail("Save must fail");
} catch (DataIntegrityViolationException e) {
Assertions.assertThat(entity.getId()).isNotNull();
}
}
}
A possible solution is to use org.hibernate.event.spi.PreInsertEventListener where we can bind the transaction with a processor that will clear your entity if transaction is failed.
Example:
#Component
public class IdentifierCleaner implements PreInsertEventListener {
#Autowired
private EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(this);
}
#Override
public boolean onPreInsert(PreInsertEvent event) {
Object entity = event.getEntity();
event.getSession().getActionQueue().registerProcess(((success, session) -> {
if (!success) {
event.getPersister().resetIdentifier(
entity,
event.getId(),
event.getPersister().getVersion(entity),
event.getSession()
);
}
}));
return false;
}
}

Lazy Loading of One to Many Doesnt Work

I have 2 entities - Media and Keyword with one to many relationship. I though I marked the fetch to Lazy Madia is still fetching all the keywords.
I am using spring 3.2 Hibernate 4.3.4
Media Entity:
#Entity
public class Media {
#Id
#NotNull
#Column(name = "mediaid")
private String mediaId;
#NotNull
#OneToMany(cascade = CascadeType.ALL, mappedBy = "media", fetch = FetchType.LAZY)
private List<Keyword> keywords;
public List<Keyword> getKeywords() {
return keywords;
}
public void setKeywords(List<Keyword> keywords) {
if ( this.keywords!=null && this.keywords.size()>0 )
this.keywords.addAll(keywords);
else
this.keywords = keywords;
for (Keyword keyword : keywords) {
keyword.setMedia(this);
}
}
}
Keyword Entity:
#Entity(name = "keywords")
public class Keyword {
#Id
#NotNull
#Column(unique = true)
private String idKey;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "idmedia")
private Media media;
public Media getMedia() {
return media;
}
public void setMedia(Media media) {
this.media = media;
if (idKey == null || idKey.isEmpty())
setIdKey(this.media.getMediaId());
else
setIdKey(this.media.getMediaId() + "_" + idKey);
if (!media.containsKeyword(this))
media.getKeywords().add(this);
}
}
I can see the entities being fetch in the show_sql output but also this test fails
#WebAppConfiguration
#ContextConfiguration(classes = {PersistenceConfig.class})
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class MediasRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
#Autowired
MediasRepository mediasRepository;
#PersistenceContext
EntityManager manager;
public void thatMediaLazyLoadsKeywords() throws Exception {
PersistenceUnitUtil unitUtil = manager.getEntityManagerFactory().getPersistenceUnitUtil();
Media media = DomainFixtures.createMedia();
Media savedMedia = mediasRepository.save(media);
Media retrievedMedia = mediasRepository.findOne(savedMedia.getMediaId());
assertNotNull(retrievedMedia);
assertFalse(unitUtil.isLoaded(retrievedMedia, "keywords"));
}
}
Configuration:
#EnableTransactionManagement
public class PersistenceConfig {
static Logger logger = LoggerFactory.getLogger(PersistenceConfig.class);
#Autowired
private Environment env;
// #Value("${init-db:false}")
private String initDatabase = "false";
#Bean
public DataSource dataSource()
{
logger.info("Starting dataSource");
BasicDataSource dataSource = new BasicDataSource();
logger.info("jdbc.driverClassName"+ env.getProperty("jdbc.driverClassName"));
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
logger.info("End dataSource");
return dataSource;
}
#Bean
public EntityManagerFactory entityManagerFactory() throws SQLException
{
logger.info("Starting entityManagerFactory");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(Boolean.TRUE);
vendorAdapter.setShowSql(Boolean.parseBoolean(env.getProperty("hibernate.show_sql")));
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("....");
factory.setDataSource(dataSource());
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.enable_lazy_load_no_trans", true);
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
factory.setJpaProperties(jpaProperties);
factory.afterPropertiesSet();
logger.info("End entityManagerFactory");
return factory.getObject();
}
.
.
.
}
You test is running in the context of a single transaction.
The Keywords are not being fetched, they're still in the persistence context from when you created them.
Try adding a manager.clear() after your save method call.
FetchType.LAZY is just a hint to the persistence provider, it is not a setting you can rely on. The persistence provider is free to load the attributes whenever it sees fit.
Thus you cannot reliably test whether lazy loading works as expected.

Spring+Hibernate Integration: Transaction Manager doesn't work using #Transactional

Spring configuration is done in code using annotations instead of XML file. I was trying to query some data and create new columns into ORACLE database through hibernate. My problem is that hibernate only generates SELECT queries, when I use sessionFactory.getCurrentSession().save(), hibernate doesn't generate INSERT queries. I think this could be a transaction issue but couldn't find where went wrong. I'll put code below and any help will be appreciated.
That's the main configuration class:
#Configuration
#ComponentScan
#EnableAutoConfiguration
#EnableTransactionManagement
#PropertySource({ "file:src/main/resources/Config/database.properties" })
public class QCWordImportExportTool {
#Autowired
private Environment env;
#Autowired
private WietHibernateInterceptor wietHibernateInterceptor;
/**
* main, says it all! :)
*
* #param args
*/
public static void main(String[] args) {
SpringApplication.run(QCWordImportExportTool.class, args);
}
#Bean
MultipartConfigElement multipartConfigElement() {
MultiPartConfigFactory factory = new MultiPartConfigFactory();
factory.setMaxFileSize("10MB");
factory.setMaxRequestSize("1024KB");
return factory.createMultipartConfig();
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(restDataSource());
sessionFactory.setPackagesToScan(new String[] { "com.ciena.prism.almtools.wiet" });
sessionFactory.setHibernateProperties(hibernateProperties());
sessionFactory.setEntityInterceptor(this.wietHibernateInterceptor);
return sessionFactory;
}
#Bean
public DataSource restDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
#Bean
public HibernateTransactionManager transactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties hibernateProperties() {
return new Properties() {
private static final long serialVersionUID = 1L;
{
setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
setProperty("hibernate.globally_quoted_identifiers",
env.getProperty("hibernate.globally_quoted_identifiers"));
setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
setProperty("hibernate.format_sql", env.getProperty("hibernate.format_sql"));
}
};
}
}
The Service class is an interface and I'll post the Service Impl class with the main method:
#Service
#Transactional(readOnly = true)
public class ImportExportManagerImpl implements ImportExportManager {
private TestFacade testFacade;
private TestFolderFacade testFolderFacade;
private UserManager userManager;
#Autowired
SessionFactory sessionFactory;
#Autowired
RequirementCoverageDAO requirementCoverageDao;
#Autowired
RequirementDAO requirementDao;
#Autowired
WietHibernateInterceptor wietHibernateInterceptor;
#Autowired
public ImportExportManagerImpl(TestFacade testFacade, TestFolderFacade testFolderFacade,
UserManager userManager) {
this.testFacade = testFacade;
this.testFolderFacade = testFolderFacade;
this.userManager = userManager;
}
/*
* (non-Javadoc)
*
* #see com.ciena.prism.almtools.wiet.managers.ImportExportManager#importTestCases(java.lang.String,
* java.lang.String, java.util.List)
*/
#Override
#Transactional(readOnly = false)
public void importTestCases(String domain, String project, List<TestCase> testCases)
throws RequestFailureException, RESTAPIException, InvalidDataException {
System.out.println("Start to import...");
setDBSchema(domain, project);
for (TestCase testCase : testCases) {
TestFolder testFolder = retrieveTestFolderFromPath(domain, project, testCase.getFolderPath());
Test test = new Test(testCase, testFolder);
ALMEntity almEntity = new ALMEntity(test);
Test existingTest = getExistingTest(domain, project, test);
if (existingTest == null) {
existingTest = new Test(testFacade.createEntity(domain, project, almEntity));
} else {
testFacade.updateEntity(domain, project, existingTest.getId(), almEntity);
}
System.out.println(existingTest.getName());
/* Create Requirement_Coverage using test and doors_object_ids */
List<String> doors_object_ids = testCase.getDoors_object_ids();
for (String doors_object_id : doors_object_ids) {
List<Requirement> requirementList = requirementDao.findAllFromDoorsobjectid(doors_object_id);
if (requirementList != null && !requirementList.isEmpty()) {
System.out.println("*************Requirement:" + doors_object_id + " not null");
/* check if the coverage already exist */
Requirement requirement = requirementList.get(0);
List<RequirementCoverage> requirementCoverageList = requirementCoverageDao
.findAllFromTestIdReqId(Integer.parseInt(existingTest.getId()),
requirement.getReqId());
if (requirementCoverageList == null || requirementCoverageList.isEmpty()) {
System.out.println("**************Creating new requirement coverage");
/* create a new Requirement Coverage Object */
RequirementCoverage requirementCoverage = new RequirementCoverage();
requirementCoverage.setRequirement(requirement);
requirementCoverage.setEntityId(Integer.parseInt(existingTest.getId()));
requirementCoverage.setEntityType("TEST");
requirementCoverageDao.create(requirementCoverage);
System.out.println("*********assigned DB id: " + requirementCoverage.getId());
}
} else {
throw new InvalidDataException("Requirement Management Tool Id : " + doors_object_id
+ " doesn't exist in QC");
}
}
}
}
}
And here's the DAO impl class:
#Repository
public class RequirementCoverageDAOImpl implements RequirementCoverageDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public Integer create(RequirementCoverage requirementCoverage) {
return (Integer) sessionFactory.getCurrentSession().save(requirementCoverage);
}
}
Then Entity Class:
#Entity
#Table(schema = "wiet", name = "REQ_COVER")
public class RequirementCoverage {
#SuppressWarnings("unused")
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "req_cover_id_gen", sequenceName = "wiet.REQ_COVER_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "req_cover_id_gen")
#Column(name = "RC_ITEM_ID", unique = true, nullable = false)
private Integer id;
#OneToOne
#JoinColumn(name = "RC_REQ_ID", nullable = false)
private Requirement requirement;
#Column(name = "RC_ENTITY_ID", nullable = false)
private Integer entityId;
#Column(name = "RC_ENTITY_TYPE", nullable = false)
private String entityType;
....setters and gettters...
}
Hope I have put this clear and thanks for reading.
WietHibernateInterceptor is used to change schema dynamically:
#Component
public class WietHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 1L;
private String schema;
#Override
public String onPrepareStatement(String sql) {
String prepedStatement = super.onPrepareStatement(sql);
if (prepedStatement.toLowerCase().contains("wiet.".toLowerCase())) {
/* As #SequenceGenerator ignores schema, sequence squery is manually set to be correct */
prepedStatement = prepedStatement.replaceAll("`", "\"");
prepedStatement = prepedStatement.replaceAll("wiet.", this.schema + "\".\"");
}
/* Change schema dynamically */
prepedStatement = prepedStatement.replaceAll("wiet", this.schema);
return prepedStatement;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
}
Hibernate will only generate the INSERT statements when it flushes the session. It will flush the session in the following scenarios
When the #Transactional method ends,
When it reaches its batch limit (if configured for batch inserts) or,
When you call a query that it detects needs the session to be
flushed eg you call a count() method. Hibernate would flush the
transactional session and commit it so that count() returns an
accurate value of records.
My only ideas is that an exception is being thrown that isn't being caught (but I would have expected it to show in your logs or your console) which is causing your transaction to roll back, or that for some reason the #Transactional session isn't being created or maintained or something.
1st thing I'd try, remove the Hibernate dialect from your hibernate properties. Hibernate does a fantastic job determining which dialect to use based on the driver you give it, and (especially with Oracle drivers) if you force it to use a different dialect it can produce strange errors.
2nd thing I'd try is to determine if there are any SQLException's being thrown by Hibernate. I'd assume that you have already turned on your hibernate logging using Log4J or whatever logging provider you are using. See if you can find an SQLException being thrown.
Your #Transaction annotation says readOnly = true. That means only read is allowed in that transaction. Remove readOnly = true.
Also look at the Spring Transaction management 9.5.6. Using #Transactional.

Categories

Resources