Spring Data and #Transactional does not rollback when there are multiple saves - java

I use:
Spring data (4.x)
HikariCP
Hibernate (I use EntityManager)
Consider the following repositories:
public interface TestModelRepository extends JpaRepository<TestModel, Long> {
}
public interface TestModelRepository2 extends JpaRepository<TestModel2, Long> {
}
and the following service:
#Service
static class Svc {
#Autowired
private TestModelRepository modelRepository;
#Autowired
private TestModelRepository2 modelRepository2;
#Transactional
public void insertWithException() {
assertThat(TransactionAspectSupport.currentTransactionStatus()).isNotNull();
modelRepository.save(new TestModel("any"));
modelRepository2.save(new TestModel2("unique"));
modelRepository2.save(new TestModel2("unique"));
}
}
The second save in repository 2 throws DataIntegrityViolationException because the provided value is not unique. Transaction should rollback everything in this method as it is annotated with #Transactional, hovewer it does not.
TestModel and one of TestModel2 are persisted. Actually they are persisted to the database just after each save() call, so the values are inserted into the database even if the #Transactional method did not complete yet (I verified it by placing a breakpoint and logging into the database). It looks to me like autocommit is set to true, hovewer I set it to false (in HikariCP config).
Here is my java-based configuration (fragments):
#Bean
PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
JpaVendorAdapter vendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setDatabasePlatform(MySQL5Dialect.class.getName());
return adapter;
}
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setJpaVendorAdapter(vendorAdapter);
entityManagerFactory.setPackagesToScan(packagesToScan);
entityManagerFactory.setJpaProperties(properties);
Main question: why the transaction does not rollback everything?
Additional questions:
when the data should be commited to the database? When the transaction is commited or anytime?
is the connection kept during the whole transaction, or can it be returned to the pool in the middle of transaction and then another connection is requested if needed?
Spring Data doesn't require #Transactional on repositories, what are the transaction parameters then (propagation and isolation)?

You may have autocommit on the connection or in the db config. I also noticed you're using mysql. Make sure your schema and tables are InnoDB not MyISAM

Related

Why do SimpleJdbcCall igronre #Transactional annotation

I want to do some DB related actions in service method. Initialy it looks like this:
#Override
#Transactional
public void addDirectory(Directory directory) {
//some cheks here
directoryRepo.save(directory);
rsdhUtilsService.createPhysTable(directory);
}
Firs method directoryRepo.save(directory); is just simple JPA save action, second one rsdhUtilsService.createPhysTable(directory); is JDBCTemplate stored procedure call from it's own service. The problem is: if any exceptions accures within JPA or SimpleJdbcCall action, transaction will rollback and nothig related to JPA won't be persited, but if exception occures only within JPA action, result of SimpleJdbcCall won't be affected by transaction rollback.
To illustrate this behaviour I've remove JAP action, mark #Transactional as (readOnly = true) and moved all JDBCTemplate related logic from another service to current one.
#Service
public class DirectoriesServiceImpl implements DirectoriesService {
private final DirectoryRepo directoryRepo;
private final MapSQLParamUtils sqlParamUtils;
private final JdbcTemplate jdbcTemplate;
#Autowired
public DirectoriesServiceImpl(DirectoryRepo directoryRepo, MapSQLParamUtils sqlParamUtils, JdbcTemplate jdbcTemplate) {
this.directoryRepo = directoryRepo;
this.sqlParamUtils = sqlParamUtils;
this.jdbcTemplate = jdbcTemplate;
}
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
}
As a result #Transactional annotation is ignored and I can see new records persisted in DB.
I've got only one DataSource configured via application.properties, and here is how JDBCTemlate configured
#Component
class MapSQLParamUtils {
private final DataSource dataSource;
#Autowired
MapSQLParamUtils(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
So my questions are: why do #Transactional ignored by SimpleJdbcCall and how to configure JPA and JDBCTemlate to use same transaction manager.
UPDATE:
This is how I use this service in controller
#RestController
#RequestMapping(value = "/api/v1/directories")
public class DirectoriesRESTControllerV1 {
private final DirectoriesService directoriesService;
#Autowired
public DirectoriesRESTControllerV1(DirectoriesService directoriesService) {
this.directoriesService = directoriesService;
}
#PostMapping
#PreAuthorize("hasPermission('DIRECTORIES_USER', 'W')")
public ResponseEntity createDirectory(#NotNull #RequestBody DirectoryRequestDTO createDirectoryRequestDTO) {
Directory directoryFromRequest = ServiceUtils.convertDtoToEntity(createDirectoryRequestDTO);
directoriesService.addDirectory(directoryFromRequest);
return ResponseEntity.noContent().build();
}
}
As mentioned earlier, the problem here is that JPA does not execute sql queries at once repository methods called. To enforce it you can use explicit entityManager.flush():
#Autowired
private javax.persistence.EntityManager entityManager;
...
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
entityManager.flush();
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
To see real SQL queries by hibernate you can enable option show_sql, in case if your application is spring-boot, this configuration enables it:
spring.jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging.level:
org.hibernate.SQL: DEBUG
Regarding transaction manager. In case if entityManager flush is not enough, you may need the composite transaction manager, that handles both JPA and DataSource. Spring data commons has ChainedTransactionManager. Note: you should be careful with it. I used it this way in my project:
#Bean(BEAN_CONTROLLER_TX)
public PlatformTransactionManager controllerTransactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(BEAN_ANALYTICS_TX)
public PlatformTransactionManager analyticsTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* Chained both 2 transaction managers.
*
* #return chained transaction manager for controller datasource and analytics datasource
*/
#Primary
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier(BEAN_CONTROLLER_TX) PlatformTransactionManager controllerTransactionManager,
#Qualifier(BEAN_ANALYTICS_TX) PlatformTransactionManager analyticsTransactionManager) {
return new ChainedTransactionManager(controllerTransactionManager, analyticsTransactionManager);
}
Please try this :
#Transactional(rollbackFor = Exception.class)
public void addDirectory(Directory directory){
#Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database.
So if you throw an Exception or a subclass of it, always use the above with the #Transactional annotation to tell Spring to roll back transactions if a checked exception occurs.
It's very simple, just use the following with #Transactional:
#Transactional(rollbackFor = Exception.class)

Why does JpaTransactionManager reset the read-only flag on commit?

I'm trying to set up a simple Spring Boot project with a JPA repository on top of a read-only data source, and I would like to propagate the read-only flag as hint to the underlying JDBC driver for performance optimizations, as per Spring Data JPA Reference.
The repository implementation looks as follows:
public interface MyReadOnlyRepository extends JpaRepository<String, String> {}
This is the configuration class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "readOnlyEntityManagerFactory",
transactionManagerRef = "readOnlyTransactionManager",
basePackageClasses = ReadOnlyDbConfig.class)
public class ReadOnlyDbConfig {
#Bean(name = "readOnlyDataSource")
#ConfigurationProperties(prefix = "spring.datasource.ro")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "readOnlyEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("readOnlyDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(MyEntity.class)
.persistenceUnit("readOnly")
.build();
}
#Bean(name = "readOnlyTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("readOnlyEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
and the relevant YML properties:
spring:
datasource:
ro:
url: jdbc:postgresql://...
username: ...
password: ...
driver-class-name: org.postgresql.Driver
default-read-only: true
To test it, I have written a simple test which runs against a dockerised PostgresSQL instance:
#SpringBootTest(classes = TestApplication.class)
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = { PostgresInDockerInitializer.class })
public class MyReadOnlyStoreTest {
#Inject
private MyReadOnlyRepository repo;
#Test(expected = RuntimeException.class)
public void testIsReadOnly() {
repo.save("test");
}
}
I have called the save just for the sake of testing whether the flag is set: I appreciate the read-only flag is only meant as a hint for the JDBC driver, not as a protection mechanism against write operations. The test throws an exception, as expected ("cannot execute INSERT in a read-only transaction").
My problem is that, as soon as a transaction completes successfully, the JPA transaction manager resets the connection read-only flag to false. So, for example, this test fails:
#Test(expected = RuntimeException.class)
public void testIsReadOnly() {
repo.findAll(); // <-- on commit, the transaction manager resets the read-only flag to false
repo.save("test");
}
Note that the same happens if the repository is annotated with #Transactional(readOnly=true) as per the Spring Data JPA Reference.
Could somebody please explain why the transaction manager does so? Is there an easy/better way to avoid the flag being reset, other than setting the transaction status to rollback-only?
Thanks for your help.

No transactions when java-configured spring app is running under Wildfly 9

We are updating an old Spring application to use java-config instead of XML.
The application runs fine during unit tests, but when deployed under Wildfly it seems that the transactions are inactive and the entity manager is never closed : we don't see inserts/updates being sent to the DB, and despite loggers org.springframework.transaction and
org.springframework.orm.jpa are set to DEBUG we are not getting traces of transaction begin/end.
We are using Wildfly 9.0.2 with wildfly BOM (=> Hibernate 4.3.10) and Spring 4.3.7.
We have two application modules (.war) and a persistence module (.jar) shared between them.
The persistence module holds the JpaConfig.java and DaoConfig.java configuration classes :
#Configuration
#EnableTransactionManagement(proxyTargetClass = true)
public class JpaConfig {
#Bean(destroyMethod = "close")
public EntityManagerFactory entityManagerFactory(DataSource datasource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(datasource);
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.setJpaDialect(new HibernateJpaDialect());
entityManagerFactory.setPackagesToScan("my.package.for.entities");
entityManagerFactory.setPersistenceUnitName("my-pu");
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getObject();
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setGenerateDdl(false);
adapter.setShowSql(LOG.isDebugEnabled());
return adapter;
}
#Bean
public DataSource dataSource() {
return new JndiDataSourceLookup().getDataSource("java:/appDS");
}
protected Properties jpaProperties() {
Properties props = new Properties();
props.setProperty("hibernate.hibernate.dialect", MySQL5InnoDBDialect.class.getName());
props.setProperty("hibernate.show_sql", LOG.isDebugEnabled() ? "true" : "false");
props.setProperty("hibernate.format_sql", "false");
return props;
}
}
#Configuration
#ComponentScan("my.package.for.repositories")
#EnableTransactionManagement(proxyTargetClass = true)
public class DaoConfig {
...
}
We've been trying multiple variations of the above (returning LocalContainerEntityManagerFactoryBean directly instead of calling afterPropertiesSet + getObject and returning the EntityManager, with and without a persistence.xml in META-INF, with and without a "Dependencies" manifest entry, being less redundant between Configuration classes, ...), without success.
Both WARs have their own configuration classes, all of which import JpaConfig.java and are annotated with #EnableTransactionManagement, such as :
#Configuration
#Import({ SecurityConfig.class, ServicesConfig.class, ControllerConfig.java, JpaConfig.class, DaoConfig.class })
public class RootConfig {
...
}
#Configuration
#EnableWebMvc
#EnableTransactionManagement(proxyTargetClass = true)
#ComponentScan("com.my.controller")
public class ControllerConfig extends WebMvcConfigurerAdapter {
...
}
#Configuration
#EnableWebSecurity
#PropertySource("classpath:security.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
#Configuration
#EnableAspectJAutoProxy
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan("com.my.services")
#Import(DaoConfig.class)
...
}
All controllers are annotated with #Transactional, so I would expect Spring to create a new transaction whenever an endpoint is called, and flush and close the EM + commit the transaction when the methods return. But this doesn't happen : here is an extract of our logs :
INFO [RequestProcessingTimeInterceptor] [Start call] POST http://server/web/api/rest/catalog/2
INFO [stdout] Hibernate: select .... from CATALOG catalog0_ where catalog0_.id=?
INFO [CatalogServiceImpl] Updating catalog #2...
INFO [CatalogServiceImpl] Catalog #2 updated !
INFO [RequestProcessingTimeInterceptor] [Call took 23ms] POST http://server/web/api/rest/catalog/2
There should be an update statement somewhere.
Am I missing something obvious ?
We finally solved this issue.
Actually, the above configuration is correct. Transactions are properly created. The reason why the changes were not flushed to the DB is that someone (mistakenly) added a #Immutable annotation to the entity.
Hibernate doesn't log any warning, and doesn't throw, when an #Immutable entity is updated. So in case you are having the same problem... check the annotations on the entity.
In case any Hibernate maintainer finds this answer : it'd be nice that hibernate logs a warning by default when an immutable entity is updated. That would make it easier to spot such coding errors.

Configure transaction in Spring 4.1.5 without XML

I'm writing application which connects with Oracle Database. I call function from DB which inserts new records to table. And after this callback I can decide what I want to do: commit or rollback.
Unfortunalety I'm new in Spring, so I have problems with configuration. And what's more I want to make this configuration in Java class, not in XML. And here I need your help.
UPDATED CODE:
ApplicationConfig code:
#Configuration
#EnableTransactionManagement
#ComponentScan("hr")
#PropertySource({"classpath:jdbc.properties", "classpath:functions.properties", "classpath:procedures.properties"})
public class ApplicationConfig {
#Autowired
private Environment env;
#Bean(name="dataSource")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driver"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
dataSource.setDefaultAutoCommit(false);
return dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
return jdbcTemplate;
}
#Bean(name="txName")
public PlatformTransactionManager txManager() {
DataSourceTransactionManager txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource());
return txManager;
}
}
I have Dao and Service, where both implements proper interface.
Service implementation:
#Service
public class HumanResourcesServiceImpl implements HumanResourcesService {
#Autowired
private HumanResourcesDao hrDao;
#Override
public String generateData(int rowsNumber) {
return hrDao.generateData(rowsNumber);
}
#Override
#Transactional("txName")
public void shouldCommit(boolean doCommit, Connection connection) throws SQLException {
hrDao.shouldCommit(doCommit, connection);
}
}
Dao implementation:
#Repository
public class HumanResourcesDaoImpl implements HumanResourcesDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall generateData;
#Autowired
public HumanResourcesDaoImpl(JdbcTemplate jdbcTemplate, Environment env) {
this.jdbcTemplate = jdbcTemplate;
generateData = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName(env.getProperty("procedure.generateData"));
}
#Override
public String generateData(int rowsNumber) {
HashMap<String, Object> params = new HashMap<>();
params.put("i_rowsNumber", rowsNumber);
Map<String, Object> m = generateData.execute(params);
return (String) m.get("o_execution_time");
}
#Override
#Transactional("txName")
public void shouldCommit(boolean doCommit, Connection connection) throws SQLException {
if(doCommit) {
connection.commit();
} else {
connection.rollback();
}
}
}
Main class code:
public class Main extends Application implements Initializable {
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
hrService = context.getBean(HumanResourcesService.class);
BasicDataSource ds = (BasicDataSource)context.getBean("dataSource");
Connection connection = ds.getConnection();
//do something and call
//hrService.generateData
//do something and call
//hrService.shouldCommit(true, connection);
//which commit or rollback generated data from previoues callback
}
}
UPDATE:
I think that the problem is with connection, because this statement:
this.jdbcTemplate.getDataSource().getConnection();
creates new connection, so then there is nothing to commit or rollback. But still I can't figure why this doesn't work properly. No errors, no new records...
What is wierd, is that when I debuged connection.commit(); I found out that in DelegatingConnection.java, parameter this has proper connection, but there is something like:
_conn.commit();
and _conn has different connection. Why?
Should I in some way synchronize connection for those 2 methods or what? Or this is only one connection? To be honest, I'm not sure how it works exactly. One connection and all callbacks to stored procedures are in this connection or maybe with each callback new connection is created?
Real question is how to commit or rollback data from previous callback which do insert into table?
One easy way to do this is to annotate the method with #Transactional:
#Transactional
public void myBeanMethod() {
...
if (!doCommit)
throw new IllegalStateException(); // any unchecked will do
}
and spring will roll all database changes back.
Remember to add #EnableTransactionManagement to your spring application/main class
You can use #Transactional and #EnableTransactionManagement to setup transactions without using the XML configuration. In short, annotate the methods/classes you want to have transactions with #Transactional. To setup the transactional management you use the #EnableTransactionManagement inside your #Configuration.
See Springs docs for example on how to use both. The #EnableTransactionManagement is detailed in the JavaDocs but should match the XML configuration.
UPDATE
The problem is that you are mixing raw JDBC calls (java.sql.Connection) with Spring JDBC. When you execute your SimpleJdbcCall, Spring creates a new Connection. This is not the same Connection as the one you later try to commit. Hence, nothing happens when you perform the commit. I tried to somehow get the connection that the SimpleJdbcCall uses, but could not find any easy way.
To test this I tried the following (I did not use params):
#Override
public String generateData(int rowsNumber) {
//HashMap<String, Object> params = new HashMap<>();
//params.put("i_rowsNumber", rowsNumber);
//Map<String, Object> m = generateData.execute(params);
Connection targetConnection = DataSourceUtils.getTargetConnection(generateData.getJdbcTemplate().getDataSource().getConnection());
System.out.println(targetConnection.prepareCall((generateData.getCallString())).execute());
targetConnection.commit();
return (String) m.get("o_execution_time");
}
If I don't save the targetConnection, and instead try to get the connection again by calling DataSourceUtils.getTargetConnection() when committing, nothing happens. Thus, you must commit on the same connection that you perform the statement on. This does not seem to be easy, nor the proper way.
The solution is to drop the java.sql.Connection.commit() call. Instead, you use Spring Transactions completly. If you use #Transaction on the method that performs database call, Spring will automatically commit when the method has finished. If the method body experiences any Exception (even outside the actual database call) it will automatically rollback. In other words, this should suffice for normal Transaction management.
However, if you are doing batch processing, and wish to have more control over your transactions with commits and rollbacks, you can still use Spring. To programatically control transactions with Spring, you can use TransactionTemplate which have commit and rollback methods. Don't have time to give you proper samples, but may do so in later days if you are still stuck ;)
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages="org.saat")
#PropertySource(value="classpath:resources/db.properties",ignoreResourceNotFound=true)
public class AppConfig {
#Autowired
private Environment env;
#Bean(name="dataSource")
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
#Bean(name="entityManagerFactoryBean")
public LocalContainerEntityManagerFactoryBean getSessionFactory() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean ();
factoryBean.setDataSource(getDataSource());
factoryBean.setPackagesToScan("org.saat");
factoryBean.setJpaVendorAdapter(getJpaVendorAdapter());
Properties props=new Properties();
props.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
props.put("hibernate.hbm2ddl.auto",env.getProperty("hibernate.hbm2ddl.auto"));
props.put("hibernate.show_sql",env.getProperty("hibernate.show_sql"));
factoryBean.setJpaProperties(props);
return factoryBean;
}
#Bean(name="transactionManager")
public JpaTransactionManager getTransactionManager() {
JpaTransactionManager jpatransactionManager = new JpaTransactionManager();
jpatransactionManager.setEntityManagerFactory(getSessionFactory().getObject());
return jpatransactionManager;
}
#Bean
public JpaVendorAdapter getJpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
return hibernateJpaVendorAdapter;
}
}

How do I hook up my Hibernate 4 interceptor in Java configuration for Spring 3?

I have a Hibernate interceptor that I want to put on the onLoad() call for Hibernate. I'm doing this because I want a JPA entity to have an instance of SecureRandom injected into it by Spring. Since the JPA context and the Spring context do not mix, this is a bridge from the Spring context into the JPA context.
I have "two places" in my Java config where I setup the stuff for Hibernate 4. I've included their enteries below. According to this (https://jira.springsource.org/browse/SPR-8940) I think that to set the Hibernate interceptor programmatically I need to get access to the LocalSessionFactoryBean. Perhaps through the LocalContainerEntityManagerFactoryBean? I just have no idea how to do that, or if I need to reconfigure the way I'm setting up my Hibernate stuff. Any help would be much appreciated!
#Bean
JpaTransactionManager jpaTransactionManager(LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(localContainerEntityManagerFactoryBean.getObject());
return jpaTransactionManager;
}
#Bean(name = "LocalContainerEntityManagerFactory")
#Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(BasicDataSource jdbcConnection) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class.forName("com.mysql.jdbc.Driver").newInstance();
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(jdbcConnection);
localContainerEntityManagerFactoryBean.setPackagesToScan(this.getClass().getPackage().getName());
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.hbm2ddl.auto", "create");
localContainerEntityManagerFactoryBean.setJpaProperties(jpaProperties);
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
hibernateJpaVendorAdapter.setShowSql(true);
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter);
return localContainerEntityManagerFactoryBean;
}
#Component
public class InvitationEntityInterceptor extends EmptyInterceptor {
#Autowired
SecureRandom secureRandom;
#Override
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
if(entity instanceof Invitation) {
Invitation invitation = (Invitation) entity;
invitation.setRandom(secureRandom);
}
return false;
}
}
I've managed this that way:
Spring database configuration class:
#Bean
#DependsOn("hibernateInterceptor")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
JpaVendorAdapter jpaVendorAdapter,
HibernateInterceptor hibernateInterceptor) {
LocalContainerEntityManagerFactoryBean emfb =
new LocalContainerEntityManagerFactoryBean();
...
Properties props = new Properties();
props.put("hibernate.ejb.interceptor", hibernateInterceptor);
emfb.setJpaProperties(props);
return emfb;
}
Hibernate interceptor class:
#Component
public class HibernateInterceptor extends EmptyInterceptor {
...
#Autowired
private MyRepository myRepository;
...
}
I abandoned the approach of using some hibernate specific event based solution and instead went for using #Configuration which requires aspectJ in Spring.
See http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/aop.html
9.8.1 Using AspectJ to dependency inject domain objects with Spring
The Spring container instantiates and configures beans defined in your
application context. It is also possible to ask a bean factory to
configure a pre-existing object given the name of a bean definition
containing the configuration to be applied. The spring-aspects.jar
contains an annotation-driven aspect that exploits this capability to
allow dependency injection of any object. The support is intended to
be used for objects created outside of the control of any container.
Domain objects often fall into this category because they are often
created programmatically using the new operator, or by an ORM tool as
a result of a database query.
The #Configurable annotation marks a class as eligible for
Spring-driven configuration. In the simplest case it can be used just
as a marker annotation:

Categories

Resources