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.
Related
I am working on an application that uses Spring Session JDBC. I also am using Spring JPA for other entities. My question is, how does one configure a Spring Boot application to allow for a separate database to be used for Session storage?
I have referenced this question, but it appears the JdbcHttpSessionConfiguration constructor noted in the answer is no longer valid (I am using Spring Boot 2.1.1). Other than that, I was unable to find any documentation on the subject. I found information on how to configure Spring Session with a JDBC backing, and how to use multiple data sources in Spring, but not how to combine the two. I reckon it might involves extending JdbcHttpSessionConfiguration, but unfortunately, I am not able to figure out how to properly do so.
This is all I have thus far:
#Configuration
class SessionConfig extends JdbcHttpSessionConfiguration {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
}
However, the above attempts to create all my entity tables in the H2 store as well.
My primary datasource (PostgreSQL) is specified in my application.properties.
spring.session.store-type=jdbc
spring.datasource.url=jdbc:postgresql://localhost/auth
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driverClassName=org.postgresql.Driver
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
Thanks for any guidance.
Since Spring Boot 2.0.0 you can specify the DataSource that Spring Session should use by using annotation #SpringSessionDataSource.
Qualifier annotation for a DataSource to be injected in
JdbcOperationsSessionRepository.
The method inside the Spring JdbcHttpSessionConfiguration class that sets the desired datasource.
#Autowired
public void setDataSource(#SpringSessionDataSource ObjectProvider<DataSource> springSessionDataSource, ObjectProvider<DataSource> dataSource)
To achieve the desired result one has to configure a secondary datasource for use in Spring Session and annotate the bean with #SpringSessionDataSource. Below is the configuration that worked for me.
application.properties
session.datasource.url=jdbc:postgresql://localhost:5432/session
session.datasource.driverClassName=org.postgresql.Driver
session.datasource.username=postgres
session.datasource.password=thepassword
primary.datasource.url=jdbc:postgresql://localhost:5432/postgres
primary.datasource.driverClassName=org.postgresql.Driver
primary.datasource.username=postgres
primary.datasource.password=thepassword
The database config
#Configuration
#EnableTransactionManagement
public class DatabaseConfig {
#Bean
#Primary
#ConfigurationProperties("primary.datasource")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
public DataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Bean
#ConfigurationProperties("session.datasource")
public DataSourceProperties sessionDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#SpringSessionDataSource
public DataSource springSessionDataSource() {
return sessionDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
}
Remember to run the org/springframework/session/jdbc/schema-thedbplatform.sql schema file on your db if you're not using a embedded database. In my case I ran org/springframework/session/jdbc/schema-postgresql.sql.
If you want to use a H2 database for you session management you can remove the session.datasource... from your application.properties and configure your datasources as follows.
#Configuration
#EnableTransactionManagement
public class DatabaseConfig {
#Bean
#Primary
#ConfigurationProperties("primary.datasource")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
public DataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Bean
#SpringSessionDataSource
public EmbeddedDatabase springSessionDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("org/springframework/session/jdbc/schema-h2.sql").build();
}
}
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
Maybe it is kind of too common but still.
I have a small test project where I'm testing all the JPA stuff. Almost everywhere I'm using Spring Data and JPA repositories work just fine. But now I'm trying to make my service to save entities. The service looks something like this:
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
private EntityManagerFactory entityManagerFactory;
public SomeServiceImpl(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
#Override
#Transactional
public SomeEntity save(SomeEntity someEntity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(someEntity);
return someEntity;
}
The persistence config looks like this (I'm intentionally copying and pasting the whole config. Maybe it would help you to reproduce the error):
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories
#PropertySource({"classpath:conf/application.properties"})
public class PersistenceConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setConnectionFactoryClassName(environment.getRequiredProperty("db.driverClassName"));
dataSource.setURL(environment.getRequiredProperty("db.url"));
dataSource.setUser(environment.getRequiredProperty("db.username"));
dataSource.setPassword(environment.getRequiredProperty("db.password"));
dataSource.setFastConnectionFailoverEnabled(
Boolean.valueOf(environment.getRequiredProperty("db.fast.connect.failover.enabled")));
dataSource.setValidateConnectionOnBorrow(true);
dataSource.setSQLForValidateConnection("SELECT SYSDATE FROM DUAL");
dataSource.setONSConfiguration(environment.getRequiredProperty("db.ons.config"));
dataSource.setInitialPoolSize(Integer.valueOf(environment.getRequiredProperty("db.initial.pool.size")));
dataSource.setMinPoolSize(Integer.valueOf(environment.getRequiredProperty("db.min.pool.size")));
dataSource.setMaxPoolSize(Integer.valueOf(environment.getRequiredProperty("db.max.pool.size")));
dataSource.setAbandonedConnectionTimeout(0);
dataSource.setInactiveConnectionTimeout(60 * 25);
dataSource.setTimeToLiveConnectionTimeout(0);
dataSource.setMaxConnectionReuseTime(60 * 30L);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.setProperty("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.setProperty("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
properties.setProperty("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
return properties;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Autowired DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan("com.dropbinc.learning.jpa.model");
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
Map jpaProperties = new HashMap();
jpaProperties.put("javax.persistence.schema-generation.database.action", "drop-and-create");
entityManagerFactory.setJpaPropertyMap(jpaProperties);
entityManagerFactory.setJpaProperties(hibernateProperties());
return entityManagerFactory;
}
#Bean
public PlatformTransactionManager transactionManager(#Autowired EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
And one more where I'm planning to configurate the rest of the application (placed in the same package):
#Configuration
#ComponentScan(basePackages = "com.dropbinc.learning.jpa")
public class AppConfig {
}
I've tried to debug Spring but all that I wasn't able to detect a difference between transaction behaviour of JPA repositories and my service. I saw transaction was created and even commited. But in case of JPA repositories it got saved while in my service implementation it did generated ids but an entity didn't appeared in a database.
I'm running all the stuff in tests, autowiring the service by interface:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { AppConfig.class, PersistenceConfig.class })
public class AllTheTests {
#Autowired
SomeService someService;
...
}
Thank you very much for any suggestion!
EDIT Adding entityManager.flush() call generates nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress.
Hi i am beginner in Spring and Jpa Integration. While i have tried to configure my database connection,details handler itp. I came across a strange behavior of spring.
First of all, I have 3 config file:
1) RootConfig - contains everything but controllers
2) WebConfig - contains every Bean which is controllers annotated
3) JdbcConfig - contains Beans related with dataSource, this config is imported by RootConfig using this annotation (#Import(JdbcConfig.class)).
RootConfig looks like this:
#Configuration
#Import(JdbcConfig.class)
#ComponentScan(basePackages = "app", excludeFilters = {#ComponentScan.Filter(type = FilterType.ANNOTATION,value = {EnableWebMvc.class, Controller.class})})
public class RootConfig
{
}
JdbcConfig:
#Configuration
#PropertySource("classpath:db.properties")
#EnableTransactionManagement
public class JdbcConfig
{
#Resource
public Environment env;
#Bean
public DataSource dataSource()
{
System.out.println(env);
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(env.getProperty("dataSource.driverClassName"));
ds.setUrl(env.getProperty("dataSource.Url"));
ds.setUsername(env.getProperty("dataSource.username"));
ds.setPassword(env.getProperty("dataSource.password"));
return ds;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource ds, JpaVendorAdapter jpaVendorAdapter)
{
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(ds);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
emfb.setPackagesToScan("app.model");
return emfb;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter()
{
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.POSTGRESQL);
adapter.setShowSql(true);
adapter.setGenerateDdl(true);
adapter.setDatabasePlatform(env.getProperty("dataSource.dialect"));
return adapter;
}
#Bean
public BeanPostProcessor beanPostProcessor()
{
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public JpaTransactionManager jpaTransactionManager(EntityManagerFactory em) {
return new JpaTransactionManager(em)}}
At this moment everything works fine, Environment field is not null value and contains all defined properties. The problem appear when I am trying to add Bean PersistenceAnnotationBeanPostProcessor So when I add this Bean to JdbcConfig.class, Environment field became null but when i add this RootConfig Environment again contains all needed values. So is there any known problem with propertySource and this bean? Is PersistenceAnnotationBeanPostProcessor somehow affect on #PropertySource or #Autorwired(#Inject/#Ressource) Environment field? Is there any reason why Environment must be configure in main config and it cannot be imported from other config by #Import?
I think your problem is related with this spring issue SPR-8269.
Can you try setting up PersistenceAnnotationBeanPostProcessor bean definition as static?
I also had same problem and I solved in this way.
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: