I am using JPA (Hibernate) in a Spring project and asking myself if my class 'BooksHandler' (which is a DAO) is thread safe ?
Code for my App Config:
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(getDataSource());
emf.setPackagesToScan("jpa.models");
JpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
emf.setJpaVendorAdapter(adapter);
emf.setJpaProperties(getProperties());
return emf;
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dtSrc = new DriverManagerDataSource();
dtSrc.setDriverClassName("com.mysql.jdbc.Driver");
dtSrc.setUrl("jdbc:mysql://localhost:3306/jpa_example");
dtSrc.setUsername("dbuser1");
dtSrc.setPassword("dbuser1");
return dtSrc;
}
private Properties getProperties() {
Properties p = new Properties();
p.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
p.setProperty("hibernate.hbm2ddl.auto", "create");
p.setProperty("hibernate.show_sql", "true");
return p;
}
//auto transaction management
#Bean
public PlatformTransactionManager getTransactionManager(EntityManagerFactory emf) {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(emf);
return manager;
}
//auto exception management
#Bean
public PersistenceExceptionTranslationPostProcessor getPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
DAO class:
#Component
#Transactional
public class BooksHandler {
#PersistenceContext
private EntityManager em;
public Book createBook(String title, String isbn, int year) {
Book b = new Book();
b.setIsbn(isbn);b.setTitle(title);b.setYear(year);
em.persist(b);
System.out.println("book created: "+b.getId());
return b;
}
public Book getBook(int id) {
return em.find(Book.class, id);
}
//other CRUD methods
}
The method of BooksHandler will be used by multiple threads and I know that EntityManager itself is NOT thread-safe. But an online course I am attending does it this way. Maybe there is some behind the scene magic by spring to make it thread safe ?
Yes, it is thread-safe. Spring injects a proxy that delegates to the EM associated to the current transaction/thread. See the documentation, which says:
The injected JPA EntityManager behaves like an EntityManager fetched from an application server’s JNDI environment, as defined by the JPA specification. It delegates all calls to the current transactional EntityManager, if any. Otherwise, it falls back to a newly created EntityManager per operation, in effect making its usage thread-safe.
(emphasis mine).
If by thread safe, you mean that you won't have any errors of Dirty Read, Non Repeatable Read or Phantom Read. No, that depends wholly on your transaction isolation level, which is configure in your Hibernate properties or in each #Transactional annotation.
Otherwise, it doesn't really matter much if two different sessions or thread (if you are using #Async), can access the same class methods at the same time, since in the end, it will purely depend on your isolation level. This blog gives a good idea about when to use which isolation level.
Related
I was setting up a basic CRUD web app with JPA in plain Spring (no Spring Boot or Spring Data JPA) for educational purposes and faced a strange problem: Spring doesn't translate exceptions for my repository. According to the Spring documentation (here and here), it is sufficient to mark the repository with the #Repository annotation and Spring will automatically enable exception translation for this repository.
However, when I did so and triggered a UNIQUE constraint violation, I still was getting a JPA PersistenceException (with a Hibernate ConstraintViolationException inside) instead of the Spring DataIntegrityViolationException.
I used pure Java Spring configuration and it took me quite some time to realize that I should compare it with the XML configuration in the documentation. Compared to the pure Java configuration, the XML configuration adds a PersistenceExceptionTranslationPostProcessor into the context. When I added it manually with #Bean, it worked, but now I have a question.
Have I misconfigured something? The Spring documentation doesn't require registering that post-processor manually for pure Java configuration. Maybe there is another way to register it, say an #EnableXXX annotation?
Here is the summary of my configuration.
#Configuration
#ComponentScan("com.example.secured_crm")
public class SpringConfiguration {
// the problem is solved if I uncomment this
//#Bean
//public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
// return new PersistenceExceptionTranslationPostProcessor();
//}
}
#Configuration
#PropertySource("classpath:db.properties")
#EnableTransactionManagement
public class DataSourceConfiguration {
#Value("${jdbc.driver}")
private String driverClass;
#Value("${jdbc.url}")
private String url;
// ...
#Value("${hibernate.debug}")
private String hibernateDebug;
#Bean
public DataSource dataSource() {
var dataSource = new ComboPooledDataSource();
// ...
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
var emFactory = new LocalContainerEntityManagerFactoryBean();
emFactory.setDataSource(dataSource());
emFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emFactory.setPackagesToScan("com.example.secured_crm.entities");
var properties = new Properties();
properties.setProperty("hibernate.dialect", hibernateDialect);
properties.setProperty("hibernate.show_sql", hibernateDebug);
properties.setProperty("hibernate.format_sql", "true");
emFactory.setJpaProperties(properties);
return emFactory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
var txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
public interface UserRepository {
User findByName(String username);
List<User> findAll();
void save(User user);
boolean deleteById(int id);
User findById(int id);
}
#Repository
public class UserJpaRepository implements UserRepository {
#PersistenceContext
EntityManager em;
#Override
public void save(User user) {
if (user.getId() == null) {
em.persist(user);
} else {
em.merge(user);
}
}
// and so on...
}
By the way, when I tried to add the post-processor in DataSourceConfiguration, it disabled #PropertySource effect. So far my impression of Spring is that it's one big hack...
It requires to manually register PersistenceExceptionTranslationPostProcessor in order for the exception translation to take effect.
The documentation you mentioned simply does not updated yet to show a fully working java configuration. It should mention to register this post processor. ( So feel free to provide a PR to update the docs.).
If you check from its javadoc , it already mentioned PersistenceExceptionTranslationPostProcessor is necessary to be registered :
As a consequence, all that is usually needed to enable automatic
exception translation is marking all affected beans (such as
Repositories or DAOs) with the #Repository annotation, along with
defining this post-processor as a bean in the application context.
P.S. If you are using spring-boot , and if it detects PersistenceExceptionTranslationPostProcessor is in the class-path , it will automatically register it by default such that you do not need to register manually.
I'm creating a new service with the goal of consuming Kafka events in an idempotent manner and storing the data into a new PostgreSQL database.
The event will provide data which will be used in the composite key:
#Embeddable
public class MyCompositeKey implements Serializable {
#Column(name="field1", nullable = false)
private UUID field1;
#Column(name="field2", nullable = false)
private UUID field2;
#Column(name="field3", nullable = false)
private UUID field3;
... boilerplate Constructors/getters ...
And the Entity will be referencing it via #EmbeddedId:
#Entity
#Table
public class MyEntity implements Serializable {
#EmbeddedId private MyCompositeKey myCompositeKey;
... Columns/Constructors/getters ...
When an event is consumed, I want to let spring-data-jpa be smart enough to know whether we are replacing data from an existing MyEntity, or creating a new row.
The logic was deemed safe enough to use the CrudRepository#save method before researching the expectation of the logic within that method:
#Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
I've gotten to the point where the transactions appear to be completed, but no records are persisted to the table.
I've confirmed via debugging that the call to #save is branching into the return this.em.merge(entity) logic referenced above.
I've only found one possibly helpful blog post[1] for a similar scenario, and am lost on where to go next after it didn't seem to resolve the issue.
The only other option I can foresee is to manually go through a potential three-query execution:
findById
if exists, delete
save
Components
spring-boot-starter 2.0.6
spring-boot-starter-data-jpa 2.0.6
hibernate 5.2.x
References
[1] https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
Alright, I found the issue. All of this design was working fine, it was the configuration which was missing.
For some context - Spring Boot seems to configure default javax.sql.DataSource, default javax.persistence.EntityManagerFactory, and default org.springframework.transaction.PlatformTransactionManager beans.
My context was configured with a javax.sql.DataSource bean in order to specify a configuration prefix distinction using org.springframework.boot.context.properties.ConfigurationProperties.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
My context did not add in replacements for the dependent javax.persistence.EntityManagerFactory and org.springframework.transaction.PlatformTransactionManager beans.
The fix was to add in all of the configuration. From the docs:
You must create LocalContainerEntityManagerFactoryBean and not EntityManagerFactory directly, since the former also participates in exception translation mechanisms in addition to creating EntityManagerFactory.
The resulting configuration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.myservice");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
Update 1 (scroll down)
The setup is as follows:
Our application database is constructed and used by two separate users:
SCHEMA - User that has authority to create and grant permissions on tables and
APP - User who is granted permissions (INSERT, UPDATE, DELETE, SELECT) (by SCHEMA) for above tables to be used.
This enables us to lock any schema changes until needed so no profound changes happen through the app user.
I am running integration tests with a live Oracle database that contains both these users. on the class itself, I use the #SqlConfig(dataSource = "schemaDataSource", transactionManager = "transactionManagerSchema").
On the test method I place two #Sql that fail because in the SqlScriptsTestExecutionListener class, the transaction is not managing the same datasource. (hence the error message further below).
I've tried setting the datasource to the transaction manager manually as shown in my config class below, however some unknown process seems to override it every time. (My best guess is through the #DataJpaTest annotation but I don't know exactly which of the 11 Auto Configurations does it, as you can see I've already disabled a couple with no effect).
Test Class:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class})
#FlywayTest
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = "transactionManagerSchema")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class, TestFlywayConfig.class})
#EntityScan(basePackageClasses = BaseEnum.class)
public class NotificationTypeEnumTest {
#Autowired
private EntityManager em;
#Test
#Sql(statements = {"INSERT INTO MYAPP_ENUM (ENUM_ID, \"TYPE\", \"VALUE\") VALUES (MYAPP_ENUM_ID_SEQ.nextval, '" + NotificationTypeEnum.DTYPE + "', 'foo')"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(statements = {"DELETE FROM MYAPP_ENUM"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void canFetchNotificationTypeEnum() throws Exception {
TypedQuery<NotificationTypeEnum> query = em.createQuery("select a from NotificationTypeEnum a", NotificationTypeEnum.class);
NotificationTypeEnum result = query.getSingleResult();
assertEquals("foo", result.getValue());
assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
}
}
DataSource and TM config:
#Slf4j #Configuration #EnableTransactionManagement
public class TestDataSourceConfig {
public static final String SCHEMA_DATA_SOURCE = "schemaDataSource";
public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTransactionManager";
/*Main Datasource and supporting beans*/
#Bean #Primary #ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() { return new DriverManagerDataSource(); }
#Bean #Primary #Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
#Bean(name = SCHEMA_DATA_SOURCE) #ConfigurationProperties(prefix = "myapp.datasource.test_schema")
public DataSource schemaDataSource() { return new DriverManagerDataSource(); }
#Bean(name = SCHEMA_TRANSACTION_MANAGER) #Autowired
public PlatformTransactionManager transactionManagerSchema(#Qualifier(SCHEMA_DATA_SOURCE) DataSource dataSource) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setDataSource(dataSource);
return jpaTransactionManager;
}
}
The full error that I couldn't fit in the title is:
java.lang.IllegalStateException: Failed to execute SQL scripts for test context
...
SOME LONG STACK TRACE
...
the configured DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource] (named 'schemaDataSource') is not the one associated with transaction manager [org.springframework.orm.jpa.JpaTransactionManager] (named 'transactionManagerSchema').
When there is a single DataSource, it appears the Spring auto-configuration model works fine, however, as soon as there are 2 or more, the assumptions break down and the programmer needs to manually fill in the sudden (plentiful) gaps in configuration required.
Am I missing some fundamental understanding surrounding DataSources and TransactionManagers?
Update 1
After some debugging, I have discovered the afterPropertiesSet() method is being called on the bean I created when the TransactionManager is retrieved for use with the #Sql script annotation. This causes whatever EntityManagerFactory it owns (i.e. JpaTransactionManager.entityManagerFactory) to set the datasource according to its configured EntityManagerFactoryInfo.getDataSource(). The EntityManagerFactory itself is being set as a result of the JpaTransactionManager.setBeanFactory method being called (as it implements BeanFactoryAware).
here is the spring code:
// JpaTransactionManager.java
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (getEntityManagerFactory() == null) {
if (!(beanFactory instanceof ListableBeanFactory)) {
throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " +
"in a non-listable BeanFactory: " + beanFactory);
}
ListableBeanFactory lbf = (ListableBeanFactory) beanFactory;
setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName()));
}
}
I then tried creating my own EntityManagerFactory bean to attempt to inject it into my created transaction manager but this seems to be opening up Hibernate Specific classes and I wish to stay abstracted at the JPA level. As well as it being difficult to configure at first glance.
Finally, a JPA only solution!
The Solution was to control the creation of the EntityManagerFactoryBeans using the provided spring EntityManagerFactoryBuilder component and inject the EntityManager into the test using the #PersistenceContext annotation.
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED)
...
public class MyJUnitTest {
#PersistenceContext(unitName = "pu")
private EntityManager em;
...
#Test
#Sql(statements = {"SOME SQL USING THE PRIVILEGED SCHEMA CONNECTION"}, ...)
public void myTest() {
em.createQuery("...").getResultList() // uses the APP database user.
}
}
Below is the configuration for both datasources. The application related DataSource beans all have #Primary in their definition to disambiguate any #Autowired dependencies. there are no Hibernate specific classes needed other than the Automatic hibernate config done through the #DataJpaTest class.
#Configuration
#EnableTransactionManagement
#EnableConfigurationProperties(JpaProperties.class)
public class TestDataSourceConfig {
public static final String SCHEMA_DATA_SOURCE = "schemaDS";
public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTM";
public static final String SCHEMA_EMF = "schemaEMF";
/*Main Datasource and supporting beans*/
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DriverManagerDataSource();
}
#Bean #Primary #Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
#Bean #Primary
public LocalContainerEntityManagerFactoryBean emfBean(
EntityManagerFactoryBuilder entityManagerFactoryBuilder,
DataSource datasource,
JpaProperties jpaProperties) {
return entityManagerFactoryBuilder
.dataSource(datasource)
.jta(false)
.packages(CourseOffering.class)
.persistenceUnit("pu")
.properties(jpaProperties.getProperties())
.build();
}
#Bean(name = SCHEMA_EMF)
public LocalContainerEntityManagerFactoryBean emfSchemaBean(
EntityManagerFactoryBuilder entityManagerFactoryBuilder,
#Qualifier(SCHEMA_DATA_SOURCE) DataSource schemaDataSource,
JpaProperties jpaProperties) {
return entityManagerFactoryBuilder
.dataSource(schemaDataSource)
.jta(false)
.packages(CourseOffering.class)
.persistenceUnit("spu")
.properties(jpaProperties.getProperties())
.build();
}
#Bean(name = SCHEMA_DATA_SOURCE)
#ConfigurationProperties(prefix = "myapp.datasource.test_schema")
public DataSource schemaDataSource() { return new DriverManagerDataSource(); }
#Bean(name = SCHEMA_TRANSACTION_MANAGER)
public PlatformTransactionManager transactionManagerSchema(
#Qualifier(SCHEMA_EMF) EntityManagerFactory emfSchemaBean) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emfSchemaBean);
return jpaTransactionManager;
}
}
Actual Test Class:
#RunWith(SpringRunner.class) // required for all spring tests
#DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class}) // this stops the default data source and database being configured.
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED) // make sure the #Sql statements are run using the SCHEMA datasource and txManager in an isolated way so as not to cause problems when running test methods requiring these statements to be run.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class})
#TestExecutionListeners({
SqlScriptsTestExecutionListener.class, // enables the #Sql script annotations to work.
SpringBootDependencyInjectionTestExecutionListener.class, // injects spring components into the test (i.e. the EntityManager)
TransactionalTestExecutionListener.class}) // I have this here even though the #Transactional annotations don't exist yet as I plan on using them in further tests.
public class NotificationTypeEnumTest {
#PersistenceContext(unitName = "pu") // required to inject the correct EntityManager
private EntityManager em;
// these statements are
#Test
#Sql(statements = {"INSERT INTO MYAPP_ENUM (ENUM_ID, \"TYPE\", \"VALUE\") VALUES (MYAPP_ENUM_ID_SEQ.nextval, '" + NotificationTypeEnum.DTYPE + "', 'foo')"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(statements = {"DELETE FROM MYAPP_ENUM"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void canFetchNotificationTypeEnum() throws Exception {
TypedQuery<NotificationTypeEnum> query = em.createQuery("select a from NotificationTypeEnum a", NotificationTypeEnum.class); // notification type is just a subclass of the BaseEnum type
NotificationTypeEnum result = query.getSingleResult();
assertEquals("foo", result.getValue());
assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
}
}
noteworthy classes:
EntityManagerFactoryBuilder - I don't like factory factories, but this one served me well in creating the correct implementation of EntityManagerFactory without depending on any hibernate specific classes. may be injected with #Autowired. The builder bean itself is configured through the HibernateJpaAutoConfiguration class (extends JpaBaseConfiguration) (imported by #DataJpaTest).
JpaProperties - useful for maintaining application.properties config in the resulting entitymanagerfactories. enabled through the #EnableConfigurationProperties(JpaProperties.class) annotation above this config class.
#PersistenceContext(unitName = "...") - I can inject the correct EntityManager in my test class with this annotation.
I'm trying to learn how to use Hibernate and Spring and came into some problem. Wanted to check on my own skin how Propagation.NESTED works. And there is my code:
#Component
#Transactional
public class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyDao dao;
...
#Override
public void testNested(int id,String name) {
User user=dao.getUser(id);
user.setName(name);
notValidNested(id,name+"new");
}
#Override
#Transactional(propagation=Propagation.NESTED)
public void notValidNested(int id,String name) {
dao.getUser(id).setName(name);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
#Component
public class CompanyDaoImpl implements CompanyDao {
#PersistenceContext
private EntityManager em;
...
#Override
public User getUser(int id) {
return em.find(User.class, id);
}
}
And Spring's configuration
#Configuration
#ComponentScan(basePackages= {"util.spring.test","service","dao"})
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class SpringConfiguration {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
System.out.println("entityManagerFactory - initialization started");
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName("ORM");
entityManagerFactoryBean.getJpaPropertyMap().put(BeanValidationIntegrator.MODE_PROPERTY, ValidationMode.NONE);
return entityManagerFactoryBean;
}
#Bean
public PlatformTransactionManager transactionManager() {
System.out.println("transactionManager - initialization started");
JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory().getObject());
transactionManager.setRollbackOnCommitFailure(true);
return transactionManager;
}
}
I have read little bit about NESTED and thought that this code would (let's assume that i made companyService.testNested(7,"newName") change name of User with id 7 to "newName". Sadly name doesn't change at all. If I replace TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); with throw new RuntimeException(); and add to annotation rollbackFor=RuntimeException.class the result is the same. I have read a little bit about propagation, but sadly I have no idea whats wrong with my code.
One possibility that comes to my mind is that my driver doesn't support savepoints (that are used in NESTED) but the same thing happens when I change NESTED to REQUIRES_NEW
The problem is you are calling a method inside the same class. Spring does not have an opportunity to intercept the call and apply the #Transactional attributes. If you move it to a separate class, you should see the behavior you are looking for.
I think the issue is not with Transaction, it may be the case of detached entity. Try calling entityManager.save() or entityManager.merge() method after changing the value.
I have an application built on Spring. I let the Spring do the all #Transactional magic and everything works fine as long as I operate on my entities that are mapped to Java objects.
However, when I want to do some custom job on a table that is not mapped to any of my Java entities, I'm stuck. Some time ago, I found a solution to execute a custom query like this:
// em is instance of EntityManager
em.getTransaction().begin();
Statement st = em.unwrap(Connection.class).createStatement();
ResultSet rs = st.executeQuery("SELECT custom FROM my_data");
em.getTransaction().commit();
When I try this with the entity manager injected from Spring with the #PersistenceContext annotation, I receive almost obvious exception:
java.lang.IllegalStateException:
Not allowed to create transaction on shared EntityManager -
use Spring transactions or EJB CMT instead
I finally managed to extract non-shared Entity Manager like this:
#Inject
public void myCustomSqlExecutor(EntityManagerFactory emf){
EntityManager em = emf.createEntityManager();
// the em.unwrap(...) stuff from above works fine here
}
Nevertheless, I find this solution neither comfortable nor elegant. I just wonder if there is any other way to run custom SQL queries in this Spring-transactional-driven environment?
For those who are curious - this problem appeared when I tried to create user accounts in my application and in the related forum at once - I did not want the forum's users table to be mapped to any of my Java entities.
You can use createNativeQuery to execute any arbitrary SQL on your database.
EntityManager em = emf.createEntityManager();
List<Object> results = em.createNativeQuery("SELECT custom FROM my_data").getResultList();
The above answer still holds true but I would like to edit in some additional information that may also be relevant to people looking at this question.
While it is true that you can use the createNativeQuery method to execute native queries through an EntityManager; there is an alternative (arguably better) way of doing it if you are using the Spring Framework.
The alternative method for executing queries with Spring (that will behave with the configured transactions) is to use the JDBCTemplate. It is possible to use both the JDBCTemplate and a JPA EntityManager within the same application. The configuration would look something like this:
InfrastructureConfig.class:
#Configuration
#Import(AppConfig.class)
public class InfrastructureConfig {
#Bean //Creates an in-memory database.
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder().build();
}
#Bean //Creates our EntityManagerFactory
public AbstractEntityManagerFactoryBean entityManagerFactory(DataSource dataSource){
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return emf;
}
#Bean //Creates our PlatformTransactionManager. Registering both the EntityManagerFactory and the DataSource to be shared by the EMF and JDBCTemplate
public PlatformTransactionManager transactionManager(EntityManagerFactory emf, DataSource dataSource){
JpaTransactionManager tm = new JpaTransactionManager(emf);
tm.setDataSource(dataSource);
return tm;
}
}
AppConfig.class:
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public MyService myTransactionalService(DomainRepository domainRepository) {
return new MyServiceImpl(domainRepository);
}
#Bean
public DomainRepository domainRepository(JdbcTemplate template){
return new JpaAndJdbcDomainRepository(template);
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
}
And an example repository that would use both JPA and JDBC:
public class JpaAndJdbcDomainRepository implements DomainRepository{
private JdbcTemplate template;
private EntityManager entityManager;
//Inject the JdbcTemplate (or the DataSource and construct a new JdbcTemplate)
public DomainRepository(JdbcTemplate template){
this.template = template;
}
//Inject the EntityManager
#PersistenceContext
void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
//Execute a JPA query
public DomainObject getDomainObject(Long id){
return entityManager.find(id);
}
//Execute a native SQL Query
public List<Map<String,Object>> getData(){
return template.queryForList("select custom from my_data");
}
}
You can use EntityManager.createNativeQuery(String sql) to use direct sql code or use EntityManager.createNamedQuery(String name) to execute precompiled query.
You still use spring-managed Entity manager, but working on non managed objects