Spring Data JPA: very slow insert and select queries - java

I have a pretty simple spring-data-jpa + Hibernate application, which stores customers data in MySql Database (the full source code is posted here).
The problem is that it runs insert and select queries very slowly, comparing to what I have through mysql CLI:
insert one row takes ~3600ms,
select all (just 2 rows) takes ~1700ms
both queries take in mysql CLI about 0.12s.
There is a similar problem discussed here, however in my case the measurements are way worse (even though I don't insert batch, it's just one simple row in DB). Is there any way to improve performance in Spring JPA/Hibernate?
Another question, is there any way to reduce size of spring-data-jpa and hibernate-entitymanager? I was able to exclude byte-buddy and jandex dependencies without harm to the program, but that's only couple Mbs (the shaded jar size is down from 19.6Mb to 16.6Mb)?
UPDATE
As per request here is the code (the all sources are here):
#Entity
#AttributeAccessor("field")
#Table(name = "customer")
public class Customer implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id",unique=true, nullable=false, insertable=true, updatable=true)
#Type(type="long")
private Long id;
#Column(name = "name")
private String name;
//+ constructors, getters/setters
}
Here is Spring application and also saving (insert) customer:
public class Application {
private static ApplicationContext applicationContext;
static CustomerRepository customerRepository;
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MySQLAutoconfiguration.class);
customerRepository = ctx.getBean(CustomerRepository.class);
runTest();
}
private static void runTest () throws {
...
//insert
Customer customerJohn = customerRepository.save(new Customer("John"));
//select
Customer foundEntity = customerRepository.findOne(customerJohn.getId());
...
}
And configuration:
#Configuration
#ComponentScan
#EnableJpaRepositories (basePackages = "com.vk.dal.repository")
#PropertySource("classpath:mysql.properties")
public class MySQLAutoconfiguration {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username") != null ? env.getProperty("spring.datasource.username") : "");
dataSource.setPassword(env.getProperty("spring.datasource.password") != null ? env.getProperty("spring.datasource.password") : "");
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.vk.dal.domain");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties properties = additionalProperties();
if (properties != null) {
em.setJpaProperties(properties);
}
return em;
}
#Bean
JpaTransactionManager transactionManager(final EntityManagerFactory entityManagerFactory) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("mysql-hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("mysql-hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("mysql-hibernate.show_sql") != null ? env.getProperty("mysql-hibernate.show_sql") : "false");
return hibernateProperties;
}
}
Appreciate any help.

Related

Spring Boot two databases JPA

I have two Postgres databases and a SpringBoot application. I connect to each database and perform separate transactions on each successfully. This has working fine as long as I have been using normal queries on my secondary database.
Primary: powwow
secondary: pims
As soon as I try execute a native query on the secondary (pims) database, it thinks it is looking at the primary database (powwow).
e.g. This "merchants" table is on the secondary database (pims):
PSQLException: ERROR: relation "merchants" does not exist
If I run the same native query but over a table on the primary (powwow) database, it works fine. So I think there's a problem with my config where I define the secondary (pims) datasource.
If I run a non native query over the secondary (pims) database using a repository in the com.xxxx.powwow.entities.pims package, it works fine.
Question
How do I execute a native query using a dao in the com.xxxx.powwow.dao.pims package?
PersistencePimsAutoConfiguration.java
#Configuration
#PropertySource({"classpath:application.properties"})
#EnableJpaRepositories(
basePackages = {"com.xxxx.powwow.dao.pims", "com.xxxx.powwow.repositories.pims"},
entityManagerFactoryRef = "pimsEntityManager",
transactionManagerRef = "pimsTransactionManager")
public class PersistencePimsAutoConfiguration {
private Logger logger = LogManager.getLogger(PersistencePimsAutoConfiguration.class);
#Value("${spring.datasource1.jdbc-url}")
private String url;
#Value("${spring.datasource1.username}")
private String username;
#Value("${spring.jpa.hibernate.ddl-auto}")
private String hbm2ddl;
#Value("${spring.jpa.database-platform}")
private String platform;
#Value("${spring.jpa.properties.hibernate.dialect}")
private String dialect;
#Value("${spring.profiles.active}")
private String profile;
#Bean
#ConfigurationProperties(prefix="spring.datasource1")
public DataSource pimsDataSource() {
return DataSourceBuilder.create().build();
}
//#Bean(name = "pimsEntityManager")
#Bean
public LocalContainerEntityManagerFactoryBean pimsEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(pimsDataSource());
em.setPackagesToScan(new String[] {"com.xxxx.powwow.entities.pims"});
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", hbm2ddl);
properties.put("hibernate.dialect", dialect);
em.setJpaPropertyMap(properties);
String host = null;
try {
host = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
logger.info("Setting spring.datasource1 (pims): hibernate.hbm2ddl.auto='"+hbm2ddl+"', platform='"+platform+"', url='"+url+"', username='"+username+"', host='"+host+"', profile='"+profile+"'.");
return em;
}
#Bean
public PlatformTransactionManager pimsTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(pimsEntityManager().getObject());
return transactionManager;
}
}
BookingHistoryReportDao.java (package com.xxxx.powwow.dao.pims)
#Component
#Transactional("pimsTransactionManager")
public class BookingHistoryReportDao {
private Logger logger = LogManager.getLogger(BookingHistoryReportDao.class);
#PersistenceContext
private EntityManager entityManager;
public void executeBookingHistoryReport(Date startDate, Date endDate, List<Integer> companyIds) {
final String sql = getSQLBookingHistoryReportDao();
try {
Query qry = entityManager.createNativeQuery(sql);
List<String> merchants = qry.getResultList();
logger.info("done");
} catch (Exception e) {
logger.error("Error executing query for BookingHistoryReport.", e);
logger.info(sql);
}
}
private String getSQLBookingHistoryReportDao() {
return "select company_name from Merchants limit 100";
}
}
I managed to get this working by using the PersistenceUnitName.
e.g.
set it in the config:
em.setPersistenceUnitName("pimsPersistenceUnit");
and reference in the DAO:
#PersistenceContext(unitName = "pimsPersistenceUnit")
private EntityManager entityManager;

Annotations #ActiveProfile doesn't work in a Spring app

I'm not using spring-boot in this app.
I'm testing profiles to use different datasource in integration tests.
I have entity User as follow:
#Table(name = "user_inf")
#Entity
#NamedQuery(name="User.findById", query="select u from User u where u.id=:id")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "userName", length = 25)
private String userName;
#Column(name = "userEmail", unique = true, length = 320)
private String userEmail;
}
for that entity I have the service and dao(Service only invokes dao method)
UserDao :
#Repository
#Transactional
public class UserDaoImpl implements UserDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public User findById(Long id) {
TypedQuery<User> query = entityManager.createNamedQuery("User.findById", User.class);
query.setParameter("id", id);
return query.getSingleResult();
}
}
User service :
#Service
public class UserServiceImpl implements UserService{
#Autowired
private UserDao userDao;
#Override
public User getUser(Long id) {
return userDao.findById(id);
}
}
#Configuration
#PropertySource(value = {"classpath:database/jdbc.properties"})
#EnableTransactionManagement
#ComponentScan({"com.example.test.repository", "com.example.test.service"})
public class SpringConfig {
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_MAX_FETCH_DEPTH = "hibernate.max_fetch_depth";
private static final String PROPERTY_NAME_HIBERNATE_JDBC_FETCH_SIZE = "hibernate.jdbc.fetch_size";
private static final String PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE = "hibernate.jdbc.batch_size";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String ENTITY_MANAGER_PACKAGES_TO_SCAN = "com.example.test.entity";
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
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
#Profile("test")
public DataSource dataSourceForTest() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.test.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
#Bean
public PlatformTransactionManager jpaTransactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
return transactionManager;
}
private HibernateJpaVendorAdapter vendorAdaptor() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
return vendorAdapter;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource mainDataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdaptor());
entityManagerFactoryBean.setDataSource(mainDataSource);
entityManagerFactoryBean.setPackagesToScan(ENTITY_MANAGER_PACKAGES_TO_SCAN);
entityManagerFactoryBean.setJpaProperties(jpaHibernateProperties());
return entityManagerFactoryBean;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties jpaHibernateProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_MAX_FETCH_DEPTH, env.getProperty(PROPERTY_NAME_HIBERNATE_MAX_FETCH_DEPTH));
properties.put(PROPERTY_NAME_HIBERNATE_JDBC_FETCH_SIZE, env.getProperty(PROPERTY_NAME_HIBERNATE_JDBC_FETCH_SIZE));
properties.put(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE, env.getProperty(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE));
properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
properties.put("hibernate.hbm2ddl.auto", "none");
return properties;
}
}
Property file which is used for datasource and hibernate (jdbc.properties) contains following :
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdatabase
jdbc.username=Bruce
jdbc.password=givanchy
jdbc.test.url=jdbc:mysql://localhost:3306/testdatabase1
hibernate.max_fetch_depth = 3
hibernate.jdbc.fetch_size = 50
hibernate.jdbc.batch_size = 10
hibernate.show_sql = true
hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
Entry point for application :
public class EntryClass {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = applicationContext.getBean("userServiceImpl", UserServiceImpl.class);
User user =userService.getUser(1L);
System.out.println("userName is "+user.getUserName());
}
}
It works as it should.
But in test source
I have only one test for service to understand how profiles work
#SpringJUnitConfig(SpringConfig.class)
#ActiveProfiles("test")
class UserServiceImplTest {
#Autowired
private UserService userService;
#Test
void getUser() {
User user = userService.getUser(5L);
Assertions.assertEquals("Vector", user.getUserName());
}
}
And I get "No qualifying bean" exception because there are two beans of datasource type, but I set which profile it should use?Can you explain why It doesn't work?
When you launch it normally, the datasource bean with 'test' profile is not created. (becasue there is no test profile set.)
When you run it as a test, then both datasource beans are created. The default is created because there is no any condition on it, and the other is becasue its annotated with the test profile.
Simply add #Profile("!test") to the default bean. This way it will be created only if the test profile is NOT active.

Repositories with native queries fail in test environment - postgres, jpa, spring

I have set up integration tests for a spring boot project using test containers (sets up a docker instance with postgresql). The tests work great if the repositories that I am testing against do not use native queries. However, whenever a repository contains a native query I get the following error: ERROR: relation "my_table_here" does not exist. How do I get my test configuration to work to allow native queries?
Below is my test set up:
#RunWith(SpringRunner.class)
public class TestPostgresql {
#ClassRule
public static PostgreSQLContainer postgreSQLContainer = PostgresDbContainer.getInstance();
/**
* ************ REPOSITORIES ************
*/
#Autowired
NativeQueryRepository nativeQueryRepository;
#TestConfiguration
#EnableJpaAuditing
#EnableJpaRepositories(
basePackageClasses = {
NativeQueryRepository.class
})
#ComponentScan(
basePackages = {
"com.company.project.package.repository"
}
)
static class PostgresConfiguration {
/**
* ************ DATABASE SETUP ************
*/
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(postgreSQLContainer.getJdbcUrl());
dataSource.setUsername(postgreSQLContainer.getUsername());
dataSource.setPassword(postgreSQLContainer.getPassword());
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new JpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.company.project");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
}
EDIT: I believe this has something to do with the naming strategy?
For greater context here is an example of how the nativeQuery is used in the repository
#Repository
public interface NativeQueryRepository extends JpaRepository<NativeEvent, Long> {
#Modifying
#Transactional
#Query(value = "UPDATE native_event SET state = :state " +
"WHERE secondary_id = :secondaryId", nativeQuery = true)
void updateState(
#Param("state") String state,
#Param("secondaryId") String secondaryId);
}
I also tried update the testProperties on the static class inside TestPostgresql by adding the annotation:
#TestPropertySource(properties = {
"spring.jpa.hibernate.naming-strategy=org.springframework.boot.orm.jpa.SpringNamingStrategy"
})
However, with no change to the error received.
EDIT: add NativeEvent:
#Entity
#Table(
name = "NativeEvent",
indexes = {
#Index(name = "idx_native_event_secondary_id", columnList = "secondaryId")
}
)
#EntityListeners(AuditingEntityListener.class)
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class NativeEvent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="secondaryId", nullable=false)
private String secondaryId;
#Column(name="state")
private String state;
}
You are doing manual configuration instead of using the runtime configuration. Hence different treatment of naming strategies. Instead you should be reusing the same configuration instead of writing your own.
Either use an #SpringBootTest or #DataJpaTest and only re-configure the DataSource.
Do something with an ApplicationContextInitializer to get the JDBC properties into the ApplicationContext.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(initializers = {TestPostgresql.JdbcInitializer.class})
public class TestPostgresql {
#ClassRule
public static PostgreSQLContainer postgreSQLContainer = PostgresDbContainer.getInstance();
/**
* ************ REPOSITORIES ************
*/
#Autowired
NativeQueryRepository nativeQueryRepository;
static class JdbcInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
"spring.datasource.username=" + postgreSQLContainer.getUsername(),
"spring.datasource.password=" + postgreSQLContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
}
This will reuse the configuration from the runtime in your test. Instead of #SpringBootTest you should als be able to use #DataJpaTest(NativeQueryRepository.class) to make a sliced test for JPA only.
You assign your table name explicitly like this:
#Table(name = "NativeEvent")
but in your native query you have a different name for that table:
#Query(value = "UPDATE native_event ...)
Either remove the name attribute from your #Table annotations (assuming your naming strategy will produce names like native_event) or change table name in native query to be nativeevent or nativeEvent so in this case just remove the underscore.
Somewhat related post

hibernate didn't auto update the #ElementCollection

According to the book when i update a ElementCOllection List , i do not do the Transection.begin, the hibernate will auto commit, but i made a test on it, the result has something wrong
My Main.java is
public class Main {
private static UserService userService;
private static Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(RootConfig.class);
userService = applicationContext.getBean(UserService.class);
User user = new User("qwerty");
user.getMessages().add("hello,world");
userService.save(user);
User user1 = userService.findByName("qwerty");
user1.getMessages().add("ncjdksckds");
System.out.println(user);
}
}
and my configuration is here , coding according the book
#Configuration
#ComponentScan(basePackages = {"org.zhy"})
#EnableJpaRepositories(basePackages = {"org.zhy.repository"})
#EnableTransactionManagement
public class RootConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter ) {
LocalContainerEntityManagerFactoryBean emfb =
new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
emfb.setPersistenceUnitName("demo");
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
emfb.setJpaProperties(hibernateProperties);
emfb.setPackagesToScan("org.zhy.domain");
return emfb;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
// some setting here such as url...
return ds;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setGenerateDdl(false);
adapter.setDatabase(Database.MYSQL);
adapter.setShowSql(true);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
return adapter;
}
}
the Entity is here
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ElementCollection(fetch = FetchType.EAGER)
private List<String> messages = new ArrayList<String>();
//getter and setter
when i use user1.getMessages().add("ncjdksckds");
the database did not auto flush the new message into it , i want to know why????
Not sure which book you are referring to but the key about #ElementCollection in your case is that all operations are cascaded by default.
Assuming your service has all public methods marked as transactional, after you query for the user, it is a detached entity.. as it is from now on outside of any transactional scope.
In your code:
User user = new User("qwerty");
user.getMessages().add("hello,world");
userService.save(user); // new transaction start and finish
User user1 = userService.findByName("qwerty"); // new transaction start and finish
user1.getMessages().add("ncjdksckds"); // this change is outside of a transaction
in order to make that change persistend you would need to merge the user1 entity back into the persistence context:
userService.merge(user1);
Inside you would call:
entityManager.merge(user);

Saving entity in repository does not work SPRING

I'm trying to save entity in repository but it does not work at all. Repository is Autowired and in runtime I use saveAndFlush to save entity. I'm using PostgreSQL. Above test methods I added comments with explanation what is going on. I expected that method saveAndFlush should work but it did not. I can not find why.
#Transactional
public class TestClass{
#Autowired private MyRepository repository;
#Autowired private EntityManager entityManager;
// Working version
public void writingToRepositoryWorking() {
entityManager.getTransaction().begin();
entityManager.persist(new MyData(99));
entityManager.getTransaction().commit();
}
// not working and throws exception :
// TransactionRequiredException: no transaction is in progress
public void writingToRepositoryNotWorking() {
repository.saveAndFlush(new MyData(99));
}
// not working, no exception, no data in repository,
// but auto generated ID is incremented
public void writingToRepositoryNotWorkingToo() {
repository.save(new MyData(99));
}
}
repository interface file
#Repository
#Transactional
public interface MyRepository extends JpaRepository<MyData, Long> {}
MyData file
#Entity(name = "myData")
public class MyData {
#Id #GeneratedValue(strategy = GenerationType.AUTO) long id;
private int testValue;
public MyData() { }
public BugData(int testValue) {
this.testValue = testValue;
}
public long getId() {
return id;
}
public int getTestValue() {
return testValue;
}
}
ApplicationConfiguration file
#Configuration
#EnableJpaRepositories("com.mypackage.app")
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
#EnableWebMvc
class ApplicationConfiguration extends WebMvcConfigurationSupport {
#Value("${jdbc.url}") private String KEY_JDBC_URL;
#Value("${jdbc.username}") private String KEY_JDBC_USERNAME;
#Value("${jdbc.password}") private String KEY_JDBC_PASSWORD;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
#Autowired
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.mypackage.app");
factory.setHibernateProperties(hibernateProperties());
return factory;
}
public Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(KEY_JDBC_URL);
dataSource.setUsername(KEY_JDBC_USERNAME);
dataSource.setPassword(KEY_JDBC_PASSWORD);
return dataSource;
}
#Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.mypackage.app");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(hibernateProperties());
em.afterPropertiesSet();
return em.getObject();
}
#Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
...
}
For starter, you're actually working on 2 different EntityManager in your non-working test case:
EntityManager autowired into your test by Spring (this one is singleton and should be avoided anyway) ,other is
EntityManager created by the EntityManagerFactory configured in your ApplicationConfiguration.
At the same time, you also have another Session running along side the aforementioned 2 EntityManagers due to your configuration of Hibernate SessionFactory. Additionally, because of the configured HibernateTransactionManager, all transactions created by #Transactional are bound to the Hibernate's Session created by SessionFactory and the EntityManager used by your Repository certainly has no way to know about it. This is why TransactionRequiredException was thrown when your Repository tried to persist data.
To fix it, you may consider removing the Hibernate's SessionFactory and switch the transaction manager to a JpaTransactionManager. Then, #Transactional on your Repository will have the effect of creating a new transaction and binding it to the existing EntityManager that is known to Spring.
One side note is that the #Transactional on your TestClass doesn't help at all as the instance of this class is not instantiated and managed by Spring. To make this work, a proper configuration of transactional test class needs to be provided as described here: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html.
Hope this helps.

Categories