I try to get my hands dirty with Spring + JPA and I read Spring in Action 4th edition. Because of code samples there are incomplete I look for needed parts of code myself in web. But seems I did smth wrong because when I call EntityManager.persist() it doesn't work. No even exception thrown
My code below
DataConfig
#Configuration
#EnableTransactionManagement
public class DataConfig {
#Bean
public DataSource dataSource() {
SingleConnectionDataSource ds = new SingleConnectionDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/dbname");
ds.setSuppressClose(true);
ds.setUsername("user");
ds.setPassword("password");
return ds;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter(){
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setShowSql(true);
adapter.setGenerateDdl(false);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
return adapter;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter){
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
emfb.setPackagesToScan("spittr.domain");
return emfb;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource ds){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(ds);
return transactionManager;
}
}
Repository
#Repository
public class JpaSpittleRepository implements SpittleRepository{
#PersistenceUnit
private EntityManagerFactory emf;
//other methods...
#Transactional
public void save(Spittle spittle) {
EntityManager em = emf.createEntityManager();
em.persist(spittle);
}
}
So in this configuration save() saves nothing.
Of course if explicitly add a pair of lines of code to that method like:
#Transactional //unnecessary now
public void save(Spittle spittle) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(spittle);
tx.commit();
}
save() works fine (even without #Transactional)
Also I found that #Transactional does wrap save() method with doBegin() and doCommit() methods of JpaTransactionManager.
Where would you suggest to dig more to find the problem?
That same book offers another way of configuring Repository by replacing injection of EntityManagerFactory with EntityManager:
//#PersistenceUnit
#PersistenceContext
//private EntityManagerFactory emf;
private EntityManager em;
As explained, calling emf.createEntityManager() in each method creates new EntityManager which complicates matters concerning transactions
Now it works, though for me the root cause of problem still unclear
Related
I am using spring and repository level for connection to the database. I am using EntityManagerFactory and the jpa repository. My config:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.project.models");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
#Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
But now I have a case where I want to use the EntityManager's create method. How can I take an instance of it and also where, when I am using the Jpa repository and have no concrete class for it, that implements the methods?
I am using a repository that extends JpaRepository:
public interface MyRepository extends JpaRepository<Chat, Integer> {
}
and then using it in my service:
#Service
public class MyServiceImpl implements MyService {
private MyRepository myRepository;
public MyServiceImpl(MyRepository myRepository) {
this.myRepository = myRepository;
}
Should I make an abstract class that implements MyRepository so I don't have to implement all JpaRepository's methods and take the instance of the EntityManager there and how can I do it?
Usually you would just inject EntityManager where ever you need it. Like:
#PersistenceContext
private EntityManager em;
and just use method persist(..) like:
em.persist(myChatObject);
so there should not be a need for any special handling.
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.
I started out with this guide, to migrate our xml config to an annotation config.
The current problem is, that my test a persist seems to not actually write the data (= no transaction commit). This results in the next check to fail. The environment currently has five persistence units and according entityManagerFactories and transactionManagers.
MyTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {PersistenceJPAConfig.class}, loader = AnnotationConfigContextLoader.class)
public class MyTest {
#Autowired
private MyDao testable;
#Transactional(transactionManager = "tm1") // tried with name= and without
#Test
public void crudTest() throws Exception {
// assert that the table is empty
List<MyDO> all = testable.getAll();
assertTrue(all.isEmpty());
// write one entity
MyDO anEntity = new MyDO();
testable.persist(anEntity);
// load all entities and assert that the details match
List<MyDO> allAfterInsert = testable.getAll();
// THIS FAILS
assertFalse("The database result should not be empty.", allAfterInsert.isEmpty());
}
}
PersistenceJPAConfig.java
#Configuration
#EnableTransactionManagement
#ComponentScan("my.package")
public class PersistenceJPAConfig {
#PersistenceContext(unitName = "myPersistenceUnit") // also tried
#Bean(name = "myEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] {"my.package"});
em.setPersistenceUnitName("myPersistenceUnit");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
// ... the same for the other persistenceUnits with increasing names
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
#Bean(name = "tm1")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getNativeEntityManagerFactory());
transactionManager.setPersistenceUnitName("myPersistenceUnit");
transactionManager.afterPropertiesSet();
return transactionManager;
}
// ... the same for the other persistenceUnits with increasing names
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
return new PersistenceAnnotationBeanPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return properties;
}
}
MyDao.java
public class MyDao {
#PersistenceContext(unitName = "myPersistenceUnit")
EntityManager em;
#Override
#Transactional(transactionManager = "tm1") // also tried with different variations like above
public Long persist(final MyDO entity) {
em.persist(entity); // tried to add an em.flush(), but that throws a TransactionRequiredException: no transaction is in progress
// handling transactions would throw IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
return entity.getId();
}
}
The persistence units are located in the file META-INF/persistence.xml.
The tests worked with the xml configuration, but don't work with my current annotation config. Is there something I forgot? What more information could I provide?
I found a solution: Instead of entityManagerFactory().getNativeEntityManagerFactory() I used entityManagerFactory().getObject() and everything works as expected.
#Bean(name = "tm1")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
transactionManager.setPersistenceUnitName("myPersistenceUnit");
transactionManager.afterPropertiesSet();
return transactionManager;
}
I am not sure why though.
This is controller after creating project from activator template for Play and Spring sample.
Controller Code:
#org.springframework.stereotype.Controller
public class Application {
#Autowired
private BarService barService;
public Result addBar() {
Form<Bar> form = Form.form(Bar.class).bindFromRequest();
Bar bar = form.get();
barService.addBar(bar);
return play.mvc.Controller.redirect(controllers.routes.Application.index());
}
}
Bar Service:
#Service
#Transactional
public class BarServiceImpl implements BarService {
#PersistenceContext
EntityManager em;
#Override
public void addBar(Bar bar) {
em.persist(bar);
}
#Override
public List<Bar> getAllBars() {
CriteriaQuery<Bar> c = em.getCriteriaBuilder().createQuery(Bar.class);
c.from(Bar.class);
return em.createQuery(c).getResultList();
}
}
Spring Hibernate configuration:
#Configuration
#EnableTransactionManagement
public class DataConfig {
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setPackagesToScan("models");
entityManagerFactory.setJpaVendorAdapter(vendorAdapter);
entityManagerFactory.setDataSource(dataSource());
entityManagerFactory.setJpaPropertyMap(new HashMap<String, String>(){{
put("hibernate.hbm2ddl.auto", "create-drop");
}});
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory());
return transactionManager;
}
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Play.application().configuration().getString("db.default.driver"));
dataSource.setUrl(Play.application().configuration().getString("db.default.url"));
return dataSource;
}
}
My question is when controller calls addBar function in barService is it a blocking call? If yes then what should be the proper way of doing spring/hibernate integration in a Play application considering it is a sample code from Typesafe activator itself.
Yes, it blocks because JDBC does not have async/non-blocking support. And since Hibernate depends on JDBC, it inherits its blocking behavior. This is also documented here:
Common examples of such blocking operations are JDBC calls, streaming API, HTTP requests and long computations.
I highly recommend that you read the following documentation pages:
JavaAsync: Handling asynchronous results
Understanding Play thread pools
I also recommend that you take a look at other very similar discussions here:
https://stackoverflow.com/a/32784410/4600
I'm working with Spring and in it spring-data-jpa:1.7.0.RELEASE and hibernate-jpa-2.1-api:1.0.0.Final. My database is a MySQL. In a integration test, when I save an entity, the entityRepository.save() method does not return a update version of it (with the auto-incremented id populated for example), thus I can see it is in the database.
Here's my configuration class:
//#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "core.repository")
#Configuration
#PropertySource("classpath:application.properties")
public class JPAConfiguration {
#Autowired
private Environment env;
private
#Value("${db.driverClassName}")
String driverClassName;
private
#Value("${db.url}")
String url;
private
#Value("${db.username}")
String user;
private
#Value("${db.password}")
String password;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabasePlatform(env.getProperty("hibernate.dialect"));
final LocalContainerEntityManagerFactoryBean em =
new LocalContainerEntityManagerFactoryBean();
em.setJpaVendorAdapter(vendorAdapter);
em.setPackagesToScan(new String[]{"core.persistence"});
em.setDataSource(dataSource());
em.afterPropertiesSet();
return em;
}
#Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public JpaTransactionManager transactionManager(final EntityManagerFactory emf) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
}
Here's my test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {JPAConfiguration.class})
//#Transactional
//#TransactionConfiguration(defaultRollback = true)
public class AgencyRepositoryIntegrationTests {
#Autowired
AgencyRepository agencyRepository;
#Test
public void testThatAgencyIsSavedIntoRepoWorks() throws Exception {
Agency c = DomainTestData.getAgency();
Agency d = agencyRepository.save(c);
// here d equals to c but both have id 0.
Collection<Agency> results = Lists.newArrayList(agencyRepository.findAll());
//here results != null and it does contain the agency object.
assertNotNull(results);
}
}
I end up commenting the Transactional annotations (both in the test and in the configuration class) due to research other questions in stackoverflow, but it did not work.
Thanks
Try to uncomment your #Transactional annotation and run. I can't see any visible problem in your snippet but there is something inside me (and it's not hunger) saying that your transaction demarcation is not right. I had similar problems like this before and it was just #transaction annotations missing or transaction misconfiguration.
Use saveAndFlush method to make sure that the entity is in the database.
Make sure to add #Transactional to your methods if a NoTransactionException appears.