Springboot JPA javax.persistence.TransactionRequiredException: Executing an update/delete query - spring-boot

I use Springboot JPA to create multiple configuration with multiple dataSource. I created one configuration for Oracle database like this:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "mypackages.models.another",
entityManagerFactoryRef = "anotherEntityManagerFactory",
transactionManagerRef = "anotherTransactionManager"
)
public class AnotherJpaConfiguration {
#Qualifier("anotherDataSource")
#Autowired
DataSource dataSource;
#Bean(name = "anotherEntityManager")
public EntityManager entityManager() {
return anotherEntityManagerFactory().createEntityManager();
}
#Bean(name = "anotherEntityManagerFactory")
public EntityManagerFactory anotherEntityManagerFactory() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setJpaVendorAdapter(vendorAdapter);
emf.setPackagesToScan(new String[] { "mypackages.models.destination" }); // <- package for entities
emf.setPersistenceUnitName("anotherPersistenceUnit");
emf.setJpaProperties(properties);
emf.afterPropertiesSet();
return emf.getObject();
}
#Bean(name = "anotherTransactionManager")
public PlatformTransactionManager anotherTransactionManager() {
return new JpaTransactionManager(anotherEntityManagerFactory());
}
}
I used the entityManager to execute a native query:
#Service
#Slf4j
public class DestinationServiceImpl extends DestinationService{
#Qualifier("anotherEntityManager")
#Autowired
public EntityManager em2;
#Transactional("anotherTransactionManager")
#Override
void saveMtdoDictRefrQuery() {
joinTransaction();
String query = "MY INSERT QUERY)";
List<Object> result2 = this.em2.createNativeQuery("MY SELECT QUERY").getResultList();
log.debug(String.valueOf(result2.size())); // it's work
this.em2.createNativeQuery(query).executeUpdate(); // doesn't work
}
}
But I have this error:
javax.persistence.TransactionRequiredException: Executing an update/delete query
Any idea?
Thank you

Related

Is there a way to stop Spring from rollbacking every transaction in a test method and do a Rollback after it executes?

I'm trying to write some integration tests for an app that I write as a practise. I have a test class with #Transactional annotation on it, to stop tests from committing any changes to db. I've noticed that rollback happens for every method of JPA repository I use. When I save an entity and then try to find a values for mentioned entity I get an empty List since that entity does not exists in DB. So do I have to change my approach or is there a way to wrap all of test method contents into a single transaction that will be rolledback after test method is done running?
Service that is tested:
#Service
public class FridgeServiceImpl implements FridgeService {
private final FridgeRepository fridgeRepository;
public FridgeServiceImpl(FridgeRepository fridgeRepository) {
this.fridgeRepository = fridgeRepository;
}
#Override
public Fridge save(Fridge fridge) {
return fridgeRepository.save(fridge);
}
#Override
public List<IngredientInFridge> getAllInFridge(Long id) {
return fridgeRepository.findAllInFridge(id);
}
#Override
public List<IngredientInFridge> getAllToExpireAfterDate(LocalDate date) {
return fridgeRepository.findAllWithExpirationDateAfter(date);
}
Repository:
#Repository
#Transactional
public interface FridgeRepository extends JpaRepository<Fridge, Long> {
#Query(value = "select i from Fridge f join f.fridgeContents i where i.id.fridgeId = :fridgeId")
List<IngredientInFridge> findAllInFridge(#Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i inner join IngredientGrammage ig on i.ingredient.id = ig.ingredient.id where i.fridge.id = :fridgeId " +
"and ig.recipe.id = :recipeId")
Set<IngredientInFridge> getIngredientsAvailableForRecipe(#Param("recipeId") Long recipeId, #Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i where i.expirationDate <= :date")
List<IngredientInFridge> findAllWithExpirationDateAfter(#Param("date") LocalDate date);
Test class:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {TestConfig.class})
#Transactional
class FridgeServiceImplTest {
#Autowired
FridgeService fridgeService;
Ingredient ingredient;
Fridge fridge;
IngredientInFridge ingredientInFridge;
#BeforeEach
public void setUp() {
ingredient = new Ingredient(100, "Chicken", UnitOfMeasure.G);
fridge = new Fridge(new HashSet<>());
ingredientInFridge = new IngredientInFridge(ingredient, fridge, 50, LocalDate.of(2020, 12, 30));
fridge.addToFridge(ingredientInFridge);
}
#Test
void getAllInFridge_ThenReturn_1() {
//given
var savedFridge = fridgeService.save(fridge); //save an entity
//when
var ingredientsInFridge = fridgeService.getAllInFridge(savedFridge.getId()); //empty List since fridgeService.save(fridge) was rolledback.
//then
Assertions.assertEquals(1, savedFridge.getFridgeContents().size());
Assertions.assertEquals(1, ingredientsInFridge.size()); // failed assertion
}
Test config:
#Configuration
#EnableJpaRepositories(value = "com.calculate.calories.repository")
#PropertySource("classpath:applicationTest.properties")
#ComponentScan(basePackages = {"com.calculate.calories.service", "com.calculate.calories.repository"})
#EntityScan("com.calculate.calories.model")
public class TestConfig {
#Autowired
private Environment environment;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.calculate.calories.model");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Objects.requireNonNull(environment.getProperty("spring.datasource.driver-class-name")));
dataSource.setUrl(Objects.requireNonNull(environment.getProperty("spring.datasource.url")));
dataSource.setUsername(Objects.requireNonNull(environment.getProperty("spring.datasource.username")));
dataSource.setPassword(Objects.requireNonNull(environment.getProperty("spring.datasource.password")));
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
//JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return new DataSourceTransactionManager(dataSource());
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "none");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
I tried to use JpaTransactionManager instead of DataSourceTransactionManager but if I do so, there are no rollbacks for the tests and db is flooded with duplicate data. I've also attempted changing
#Transactional from class level to method level but it changed nothing.
I've seen this post: Does #Transactional annotation on test methods rollback every transaction in the annotated method before it gets to the end?
But when I try to use the EntityManager.flush() after saving it throws javax.persistence.TransactionRequiredException: no transaction is in progress

Spring-boot TransactionRequiredException: Executing an update/delete query

My service method is annotated with Transnational (org.springframework.transaction.annotation)
But still a "TransactionRequiredException" occurs when making an update on table Persons.
A select on the same table works fine.
#Transactional
public String myMethod(String contractNo){
myRepository.resetValues(contractNo);
}
#Repository
public interface MyRepository extends JpaRepository<Persons, Long> {
#Modifying
#Query(value = "UPDATE PERSONS SET status = 0 WHERE LOGIN_NAME like :contractNo", nativeQuery = true)
void resetValues(#Param("contractNo") String contractNo);
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = { "com.mypackage.repositories" }
)
public class EBankingDBConfig {
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.eba-datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.mypackage.model")
.persistenceUnit("myPersistenceUnit")
.build();
}
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Because there are multiple TransactionManagers beans defined in the project, you have to annotate transactions of non-primary TransactionaManagers with their names. For example, in my config above, the TransactionManager name is defined as "transactionManager".
So, the Transactional annotation on methods should look line this : #Transactional("transactionManager")

Spring Boot giving warning Custom isolation level specified but no actual transaction initiated

I am getting warning Custom isolation level specified but no actual transaction initiated when I use #Transactional annotation with isolation and propagation.But my query executes fine.Why method call is not coming under Transaction boundary.
#SpringBootApplication(scanBasePackages= {"com.mytest.txntest"})
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Application {
public static void main( String[] args )
{
SpringApplication.run(Application.class, args);
}
}
My Data Source Configuration looks like below.
#Configuration
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
#EnableJpaRepositories(basePackages = { "com.mytest.txntest" }, entityManagerFactoryRef = "testEntityManagerFactory", transactionManagerRef = "testTransactionManager")
#EnableTransactionManagement
public class TestDBConfig {
#Resource
private Environment env;
#Bean(name="testEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean testEntityManager(){
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(testDataSource());
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.DB2Dialect");
properties.setProperty("persistenceProviderClassName", "org.hibernate.ejb.HibernatePersistence");
entityManager.setJpaProperties(properties);
entityManager.setPersistenceUnitName("Test_DB");
entityManager.setPackagesToScan("com.mytest.txntest.entities");
entityManager.setJpaVendorAdapter(jpaVendorAdapter());
return entityManager;
}
#Bean(name="testDataSource")
public DataSource testDataSource() {
DataSource dataSource = DataSourceBuilder
.create()
.username(env.getProperty("test.username"))
.password(env.getProperty("test.password"))
.url(env.getProperty("test.url"))
.driverClassName(env.getProperty("test.datasource.driverClassName"))
.build();
return dataSource;
}
#Bean
public HibernateJpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateAdatper = new HibernateJpaVendorAdapter();
hibernateAdatper.setDatabasePlatform("org.hibernate.dialect.DB2Dialect");
hibernateAdatper.setShowSql(true);
return hibernateAdatper;
}
#Bean("testTransactionManager")
public JpaTransactionManager jpaTransactionManager(#Qualifier("testEntityManagerFactory") EntityManagerFactory entityMangerFactory) {
JpaTransactionManager jpsTxnManager = new JpaTransactionManager();
jpsTxnManager.setEntityManagerFactory(entityMangerFactory);
return new JpaTransactionManager();
}
}
My DAO Class looks like below.
public TestDAO {
#PersistenceContext
public void setEntityManager(EntityManager entityManager)
{
this.em = entityManager;
}
private EntityManagerFactory entityManagerFactory;
public void setEntityManagerFactory(EntityManagerFactory emf)
{
this.entityManagerFactory = emf;
em = entityManagerFactory.createEntityManager();
}
#PersistenceContext
protected EntityManager em;
#Transactional(propagation=Propagation.NOT_SUPPORTED,isolation=Isolation.READ_UNCOMMITTED,rollbackFor=Throwable.class)
public Webacct getUserNameInfo(String userName,String dob)
{
Query q = em.createNamedQuery("testQuery");
}
}
}
Am I missing any configuration. Using Spring Boot 1.5.9
You’re using “NOT_SUPPORTED” propagation level which essentially executes the code inside that method without a transaction. You should use any other propagation level such as REQUIRED or REQUIRES_NEW.

Spring boot Single entity to multiple database

Is it possible to save single entity to multiple Databse (DB1 and DB2) by spring boot.For example am having two MYSQL DB with same table while posting data i need to save the person details into two dbs at same .?or any other way doing spring .Already i created two db Connections if the entity are different means, I can save the data, but the entity are same means i cant able to do?
public class Person {
private Long id;
private String name;
private String city;
}
My tablesCREATE TABLEperson(
idBIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
nameVARCHAR(255) DEFAULT NULL,
cityVARCHAR(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=latin1
For two DBS Named as DB1 and DB2
MY Connection Config is
#Configuration
#EnableJpaRepositories(basePackages =
{"com.onlinetutorialspoint.repository.db1"},
entityManagerFactoryRef = "db1EntityManager",
transactionManagerRef = "db1TransactionManager")
public class DB1_DataSource {
#Autowired
private Environment env;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean db1EntityManager() {
LocalContainerEntityManagerFactoryBean em = new
LocalContainerEntityManagerFactoryBean();
em.setDataSource(db1Datasource());
em.setPackagesToScan(new String[]
{"com.onlinetutorialspoint.model.db1"});
em.setPersistenceUnitName("db1EntityManager");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect",
env.getProperty("hibernate.dialect"));
properties.put("hibernate.show-sql",
env.getProperty("jdbc.show-sql"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public DataSource db1Datasource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("jdbc.driver-class-name"));
dataSource.setUrl(env.getProperty("db1.datasource.url"));
dataSource.setUsername(env.getProperty("db1.datasource.username"));
dataSource.setPassword(env.getProperty("db1.datasource.password"));
return dataSource;
}
#Primary
#Bean
public PlatformTransactionManager db1TransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
db1EntityManager().getObject());
return transactionManager;
}
}
For second DB
#Configuration
#EnableJpaRepositories(basePackages =
{"com.onlinetutorialspoint.repository.db2"},
entityManagerFactoryRef = "db2EntityManager",
transactionManagerRef = "db2TransactionManager")
public class DB2_DataSource {
#Autowired
private Environment env;
#Bean
public LocalContainerEntityManagerFactoryBean db2EntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(db2Datasource());
em.setPackagesToScan(
new String[]{"com.onlinetutorialspoint.model.db2"});
em.setPersistenceUnitName("db2EntityManager");
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect",
env.getProperty("hibernate.dialect"));
properties.put("hibernate.show-sql",
env.getProperty("jdbc.show-sql"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean
public DataSource db2Datasource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("jdbc.driver-class-name"));
dataSource.setUrl(env.getProperty("db2.datasource.url"));
dataSource.setUsername(env.getProperty("db2.datasource.username"));
dataSource.setPassword(env.getProperty("db2.datasource.password"));
return dataSource;
}
#Bean
public PlatformTransactionManager db2TransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
db2EntityManager().getObject());
return transactionManager;
}
}
My Rest Controller
#RestController
public class CustomerController {
#Autowired
private PersonRepository personRepositorydb1;
#Autowired
private PersonRepository personRepositorydb2;
#RequestMapping("/save")
public Person savePersonDetails()
public savePersonDetails(#RequestBody Person person ){
personRepositorydb1.savePerson(person);
return personRepositorydb2.savePerson(person);
}
}
If i call two repository means its getting error the model name is already impoted
My Repository is
import com.onlinetutorialspoint.model.db1.Person;
#Repository
public interface PersonRepository extends CrudRepository<Person, Long>{
}
import com.onlinetutorialspoint.model.db2.Person;
#Repository
public interface PersonRepository extends CrudRepository<Person, Long>{
}
Maybe you can use the same approach in this answer
Spring Data + JPA with multiple datasources but only one set of Repositories
Here there is an example on how to use AbstractRoutingDataSource
https://www.endpoint.com/blog/2016/11/16/connect-multiple-jpa-repositories-using

Spring Data with JPA not rolling back transaction on error

We have Spring Data configured for JPA. A Service Transaction method doesn't get rolled back for an error (e.g. a DB ConstraintViolationException).
The closest I could find was this
(Transaction not rolling back) Spring-data, JTA, JPA, Wildfly10
but we don't have any XML configuration, all of our configuration is Java-based.
Essentially, a service method looks like this: no errors are caught, everything thrown.
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)
public void insertEvent() throws Exception {
// Part 1
EventsT event = new EventsT();
// populate it..
eventsDAO.save(event);
// Part 2 - ERROR HAPPENS HERE (Constraint Violation Exception)
AnswersT answer = new AnswersT();
// populate it..
answersDAO.save(answer);
}
Part 2 fails. But after the error and return, I see that the Event (Part 1) is still populated in the DB.
We also tried various combinations of #Transactional, nothing worked:
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)
#Transactional(readOnly = false)
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = ConstraintViolationException.class, readOnly = false)
Spring Data CRUD DAO Interfaces:
#Repository
public interface EventsDAO extends JpaRepository<EventsT, Integer> {
}
#Repository
public interface AnswersDAO extends JpaRepository<AnswersT, Integer> {
}
JpaConfig:
#Configuration
#EnableJpaRepositories(basePackages = "com.myapp.dao")
#PropertySource({ "file:${conf.dir}/myapp/db-connection.properties" })
public class JpaConfig {
#Value("${jdbc.datasource}")
private String dataSourceName;
#Bean
public Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("hibernate.dialect", PostgreSQL95Dialect.class.getName());
//props.put("hibernate.cache.provider_class", HashtableCacheProvider.class.getName());
return props;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.POSTGRESQL);
return hibernateJpaVendorAdapter;
}
#Bean
public PlatformTransactionManager transactionManager() throws NamingException {
return new JpaTransactionManager( entityManagerFactory().getObject() );
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource());
lef.setJpaPropertyMap(this.jpaProperties());
lef.setJpaVendorAdapter(this.jpaVendorAdapter());
lef.setPackagesToScan("com.myapp.domain", "com.myapp.dao");
return lef;
}
#Bean
public DataSource dataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup(dataSourceName);
}
}
Have there been any transaction rollback issues with Spring Data & JPA?
Believe it or not we fixed it. There were 2 parts to the solution:
1) Add #EnableTransactionManagement to JpaConfig as ledniov described, but that alone wasn't enough;
2) Also in JpaConfig in entityManagerFactory(), add the Service class package to the following setPackagesToScan. Previously, the domain object package was there, but the service object package was not. We added "myapp.service", the 2nd package.
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource());
lef.setJpaPropertyMap(this.jpaProperties());
lef.setJpaVendorAdapter(this.jpaVendorAdapter());
lef.setPackagesToScan("myapp.domain", "myapp.service"); //NOTE: Service was missing
return lef;
}
You have to add #EnableTransactionManagement annotation to JpaConfig class in order to enable Spring's annotation-driven transaction management capability.

Resources