spring-hibernate transactional dont rollback - java

The HQL statement does not roll back when I use spring+hibernate, but session.saveOrUpdate () will;
UserService
#Service
#Transactional(rollbackFor=Exception.class)
public class UserService {
#Autowired
private BaseDao dao;
public int updateTest(){
int i = dao.updateUser();
int t = 1/0;
return i;
}
}
BaseDao
#Repository
public class BaseDao {
#Autowired
private SessionFactory sessionFactory;
private Session getSession(){
return sessionFactory.getCurrentSession();
}
public int updateUser(){
int i = 0;
/* String sql = "from Student where name = 'dengbojing'";
Query query = this.getSession().createQuery(sql);*/
Student s = new Student();
s.setId(1);
s.setAddress("1");
Query query = this.getSession().createQuery("update Student s set s.address = '1'");
query.executeUpdate();
//this.getSession().update(s);
return i;
}
}
Configuration class
#Configuration
#EnableConfigurationProperties(HibernateProperties.class)
#EnableTransactionManagement(proxyTargetClass=true)
public class HibernateConfig {
#Autowired
private HibernateProperties config;
#Bean(name="sessionFactory")
public LocalSessionFactoryBean localSessionFactoryBean(){
LocalSessionFactoryBean bean = new LocalSessionFactoryBean();
bean.setDataSource(dataSource());
bean.setHibernateProperties(config.getHibernateProperties());
bean.setPackagesToScan(config.getPackageToScan());
return bean;
}
#Bean
public DataSource dataSource(){
DruidDataSource source = new DruidDataSource();
source.setDriverClassName(config.getDatasource().getDriverClassName());
source.setUsername(config.getDatasource().getUsername());
source.setUrl(config.getDatasource().getUrl());
source.setPassword(config.getDatasource().getPassword());
return source;
}
#Bean
public HibernateTransactionManager txManager(){
HibernateTransactionManager manager = new HibernateTransactionManager();
manager.setSessionFactory(localSessionFactoryBean().getObject());
manager.setDataSource(dataSource());
return manager;
}
}
Spring transaction does not support the HQL statement, the problem plagued me for 2 days, I saw someone with similar problems, but did not solve the problem

I have made some tests with exact same versions and configuration.
I have to say that the update is never persisted in the database.
As a workaround, if you can implement your functionality in this way.. try to:
Find the desired Person entity
Update required fields
Do not invoke any other methods on the session object after that.. just leave it to the framework to update the changes on the transaction commit.
Example
Person person = session.get(Person.class, 1);
person.setAddress("1")
// other fields updated
return i;
Now, without the explicit bulk update using the executeUpate() there is no chance for an implicit commit by the provider.. This is theory so check it out.

Related

Spring CrudRepository: Entities not attached with programmatic config, attached with DataJpaTest

I was testing some simple use cases using a CrudRepository and cannot explain the following:
When letting spring auto-configure my application (using #DataJpaTest on my test class + providing properties & schema via application.properties and schema.sql), all entities of my tests are correctly attached to the persistence context and any change is correctly reflected in the database.
However, when I'm providing a custom configuration, they seem to be detached and therefore changes are not reflected.
I came across this comment that mentioned that the context would close after the call to CrudRepository method returns which would explain why are the entities detached but it is then contradictory with my first example.
Could someone explain me what is the difference between the auto config and my programmatic one which would cause this?
Model and repo:
#Entity(name = "contents")
#Table(name = "contents")
public class PersistableContent
{
#Id
#GeneratedValue(generator = "id_generator", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "id_generator", sequenceName = "id_seq", allocationSize=10)
public Integer id;
#Column
public String value;
public PersistableContent() { }
public PersistableContent(String value) { this.value = value; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
}
#Repository
public interface PersistableContentRepository extends CrudRepository<PersistableContent, Integer> { }
First test and config (Entity is attached):
#Configuration
#EnableJpaRepositories(basePackages = { "jpa.repositories" })
#EntityScan("jpa.model")
public class MyTestConfiguration { }
#RunWith(SpringRunner.class)
#DataJpaTest
#ContextConfiguration(classes= {MyTestConfiguration.class})
public class PersistableContentRepositoryTest
{
#Autowired
private PersistableContentRepository persistableContentRepository;
#Test
public void updateNewlySavedSupposedlyAttachedEntityTest()
{
PersistableContent toUpdate = new PersistableContent("toUpdate");
persistableContentRepository.save(toUpdate);
toUpdate.setValue("updated");
PersistableContent updated = persistableContentRepository.findById(toUpdate.getId()).get();
assertThat(updated.getValue(), is(equalTo(toUpdate.getValue())));
}
}
Second test and config (Entity is detached and assertion failing):
#Configuration
#EnableJpaRepositories(basePackages = { "jpa.repositories" })
public class MyTestConfiguration
{
#Bean
public DataSource dataSource()
{
DriverManagerDataSource datasource = new DriverManagerDataSource();
datasource.setDriverClassName("org.h2.Driver");
datasource.setUrl("jdbc:h2:mem:default;DB_CLOSE_DELAY=-1");
datasource.setUsername("sa");
datasource.setPassword("sa");
// schema init
Resource initSchema = new ClassPathResource("schema.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema);
DatabasePopulatorUtils.execute(databasePopulator, datasource);
return datasource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
JpaVendorAdapter jpaVenderAdapter)
{
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPackagesToScan("jpa.model");
entityManagerFactoryBean.setJpaVendorAdapter(jpaVenderAdapter);
return entityManagerFactoryBean;
}
#Bean
public JpaVendorAdapter jpaVenderAdapter()
{
return new HibernateJpaVendorAdapter();
}
#Bean
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory)
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());
return transactionManager;
}
}
#RunWith(SpringRunner.class)
#Import(MyTestConfiguration.class)
public class PersistableContentRepositoryTest
{
// same test than above, the only difference is that:
// I'm not using #DataJpaTest nor #ContextConfiguration
// I'm importing my Configuration using #Import
}
EDIT:
To understand if it was related to the DataJpaTest auto-configuration, I decided to create another test class annotated with SpringBootTest and I created a new configuration annotated with SpringBootConfiguration. Rerun the same test and this time, the entities are no more attached when retrieved via the repository. I'm wondering if my whole problem is not related to what DataJpaTest does with the test class, something like making every single test as a transaction in order to clean up after each test. If this is the case, it was counter-intuitive for me as I used DataJpaTest in purpose to limit the auto-configuration to only JPA components while still expecting the repository to behave as it would do with a full auto-configuration or with a custom/programmatic one.

Why do SimpleJdbcCall igronre #Transactional annotation

I want to do some DB related actions in service method. Initialy it looks like this:
#Override
#Transactional
public void addDirectory(Directory directory) {
//some cheks here
directoryRepo.save(directory);
rsdhUtilsService.createPhysTable(directory);
}
Firs method directoryRepo.save(directory); is just simple JPA save action, second one rsdhUtilsService.createPhysTable(directory); is JDBCTemplate stored procedure call from it's own service. The problem is: if any exceptions accures within JPA or SimpleJdbcCall action, transaction will rollback and nothig related to JPA won't be persited, but if exception occures only within JPA action, result of SimpleJdbcCall won't be affected by transaction rollback.
To illustrate this behaviour I've remove JAP action, mark #Transactional as (readOnly = true) and moved all JDBCTemplate related logic from another service to current one.
#Service
public class DirectoriesServiceImpl implements DirectoriesService {
private final DirectoryRepo directoryRepo;
private final MapSQLParamUtils sqlParamUtils;
private final JdbcTemplate jdbcTemplate;
#Autowired
public DirectoriesServiceImpl(DirectoryRepo directoryRepo, MapSQLParamUtils sqlParamUtils, JdbcTemplate jdbcTemplate) {
this.directoryRepo = directoryRepo;
this.sqlParamUtils = sqlParamUtils;
this.jdbcTemplate = jdbcTemplate;
}
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
}
As a result #Transactional annotation is ignored and I can see new records persisted in DB.
I've got only one DataSource configured via application.properties, and here is how JDBCTemlate configured
#Component
class MapSQLParamUtils {
private final DataSource dataSource;
#Autowired
MapSQLParamUtils(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
So my questions are: why do #Transactional ignored by SimpleJdbcCall and how to configure JPA and JDBCTemlate to use same transaction manager.
UPDATE:
This is how I use this service in controller
#RestController
#RequestMapping(value = "/api/v1/directories")
public class DirectoriesRESTControllerV1 {
private final DirectoriesService directoriesService;
#Autowired
public DirectoriesRESTControllerV1(DirectoriesService directoriesService) {
this.directoriesService = directoriesService;
}
#PostMapping
#PreAuthorize("hasPermission('DIRECTORIES_USER', 'W')")
public ResponseEntity createDirectory(#NotNull #RequestBody DirectoryRequestDTO createDirectoryRequestDTO) {
Directory directoryFromRequest = ServiceUtils.convertDtoToEntity(createDirectoryRequestDTO);
directoriesService.addDirectory(directoryFromRequest);
return ResponseEntity.noContent().build();
}
}
As mentioned earlier, the problem here is that JPA does not execute sql queries at once repository methods called. To enforce it you can use explicit entityManager.flush():
#Autowired
private javax.persistence.EntityManager entityManager;
...
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
entityManager.flush();
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
To see real SQL queries by hibernate you can enable option show_sql, in case if your application is spring-boot, this configuration enables it:
spring.jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging.level:
org.hibernate.SQL: DEBUG
Regarding transaction manager. In case if entityManager flush is not enough, you may need the composite transaction manager, that handles both JPA and DataSource. Spring data commons has ChainedTransactionManager. Note: you should be careful with it. I used it this way in my project:
#Bean(BEAN_CONTROLLER_TX)
public PlatformTransactionManager controllerTransactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(BEAN_ANALYTICS_TX)
public PlatformTransactionManager analyticsTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* Chained both 2 transaction managers.
*
* #return chained transaction manager for controller datasource and analytics datasource
*/
#Primary
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier(BEAN_CONTROLLER_TX) PlatformTransactionManager controllerTransactionManager,
#Qualifier(BEAN_ANALYTICS_TX) PlatformTransactionManager analyticsTransactionManager) {
return new ChainedTransactionManager(controllerTransactionManager, analyticsTransactionManager);
}
Please try this :
#Transactional(rollbackFor = Exception.class)
public void addDirectory(Directory directory){
#Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database.
So if you throw an Exception or a subclass of it, always use the above with the #Transactional annotation to tell Spring to roll back transactions if a checked exception occurs.
It's very simple, just use the following with #Transactional:
#Transactional(rollbackFor = Exception.class)

Hibernate entity is updated existing row instead of creating new row using saveAndFlush

I am Using Spring data JPA, hibernate, sqlserver in a Spring Rest Application.
i) For the first request a record is inserted into data base. working fine until here.
ii) when i make another new request with new data updating existing record instead of inserting new record into data base
iii)But when application context reloads I am able to insert new record.
Here below is the code snippet.
1) Hibernate Configuration
public class HibernateConfiguration {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty
("db.driverClassName"));
dataSource.setUrl(env.getRequiredProperty("db.url"));
dataSource.setUsername(env.getRequiredProperty("db.username"));
dataSource.setPassword(env.getRequiredProperty("db.password"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan(new String[] { my.domains.package });
entityManagerFactoryBean.setJpaProperties(hibProperties());
return entityManagerFactoryBean;
}
private Properties hibProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto"));
return properties;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
2)Domain
#Entity
#Table(name="Emp_Detetail")
public class EmpDetail implements java.io.Serializable {
private static final long serialVersionUID = 7342724430491939936L;
#Column(name="EmployeeId")
#Id
#GeneratedValue
private int employeeId;
.......
}
3)JPA Respository
public interface EmpDetailRepository extends JpaRepository<EmpDetail, Integer> {
}
4) DAO
#Repository("empDetailDao")
public class EmpDetailDaoImpl implements EmpDetailDao {
#Resource
private EmpDetailRepository empDetailRepository;
#Override
#Transactional
public EmpDetail insertEmpDetails(EmpDetail empDetail) {
return empDetailRepository.saveAndFlush(archive);
}
}
5) Service Class
#Service
public class EmpDetailServiceImpl implements EmpDetailService{
#Autowired
private EmpDetailDao empDetailDao;
#Autowired
private EmpDetail empBO;
private EmpDetail toInsertEmpDetails(int active, String empName) throws
Exception {
empBO.setName(empName);
empBO.setActive(active);
empBO = empDetailDao.insertEmpDetails(empBO);
}
return empBO;
}
6) Controller code is
#RestController
public class EmpDeatilController {
#Resource
private EmpDetailService empDetailService;
#RequestMapping(value = "/insertEmpDetail", method =
RequestMethod.GET)
#ResponseBody
public EmpDetialResponse insertEmpDetail(#RequestParam("empName") String
empName, #RequestParam("active") int active) throws Exception{
return empDetailService.toInsertEmpDetails(active, empName);
}
}
Please help me.
Thanks in advance
When you insert the first entry, you save the inserted object in the field
#Autowired
private EmpDetail empBO;
in the EmpDetailServiceImpl bean. Since this is a singleton bean, when you do further calls of the method toInsertEmpDetails, it will use the saved object, update its name and active flag and persist this. Since this object then already has an id (from your first call), it will update the entry in the database instead of creating a new one. To solve that, just remove the field empBO, there is usually no need to have such a field in a service (which should be stateless).

Hibernate, Spring, querying in non-managed bean causes SessionException

I have a simple entity - Team, with name and rating properties (or maybe more).
Suppose I need to query my teams by multiple criteria.
So instead of adding multiple methods with signature like 'findByXYZAndZYX' to my service, I'd rather add following method :
Teams findTeams()
Implementation snippet:
#Autowired private SessionFactory sessionFactory;
...
#Override
public Teams getTeams() {
return new HibernateTeams(sessionFactory);
}
Now, Teams interface:
public interface Teams extends Iterable<Team> {
Teams withNameContaining(String name);
Teams withRatingGreaterThan(Integer rating);
}
and hibernate-specific implementation:
public class HibernateTeams implements Teams {
private static final String NAME_PROPERTY = "name";
private static final String RATING_PROPERTY = "rating";
private SessionFactory sessionFactory;
private Criteria criteria;
public HibernateTeams(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
criteria = getRootCriteria();
}
private Criteria getRootCriteria() {
return getCurrentSession().createCriteria(Team.class);
}
#Override
public HibernateTeams withNameContaining(String name) {
criteria.add(Restrictions.like(NAME_PROPERTY, name));
return this;
}
#Override
public Teams withRatingGreaterThan(Integer rating) {
criteria.add(Restrictions.gt(RATING_PROPERTY, rating));
return this;
}
#Override
public Iterator<Team> iterator() {
#SuppressWarnings("unchecked")
Collection<Team> result = criteria.list();
return result.iterator();
}
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
But now, using this in client code:
teamService
.getTeams()
.withNameContaining("someTeamName")
.withRatingGreaterThan(15)
I have an SessionException
org.hibernate.SessionException: Session is closed
I suppose this happens because of passing sessionFactory to non-managed class.
So there are a couple of questions here:
1) Is it possible to do this the way I wrote it? I tried to annotate my HibernateTeams with Transactional or sth, but it didn't help.
2) If I need to make my HibernateTeams spring-managed-bean and possibly inject SessionFactory into it, how can I do that? I've already tried with
#Component #Scope("prototype")
or #Configurable
but with no luck.
Thanks,

Insert Data only once after Hibernate started

I'm using hibernate 4.1.2
And I want insert some data into the table.
I know that it can be realized with sql in hibernate configuration in this way:
<property name="hibernate.hbm2ddl.import_files" value="/file1.sql,/file2.sql"/>.
But, are there any other ways insert automatically data only once in Java code after hibernate started?
I want do it like this:
public Role getRoleByName(EnumRole name)
{
return (Role) sessionFactory.getCurrentSession()
.createQuery("from Role where name = :name")
.setParameter("name", name).uniqueResult();
}
public void insertRoles(){
for(EnumRole role:EnumRole.values())
{
Role r=getRoleByName(role);
if(r==null)
{
r=new Role();
r.setName(role);
r.setDescription(role.getDescription());
sessionFactory.getCurrentSession().save(r);
}
}
EnumRole:
public enum EnumRole {
ROLE_CLIENT("РОЛЬ КЛИЕНТА"),
ROLE_ADMIN("РОЛЬ АДМИНСТРАТОРА"),
ROLE_CONSUMER("РОЛЬ КОМПАНЬОНА"),
ROLE_ANONYMOUS("НЕ АВТОРИЗОВАННЫЙ ПОЛЗОВАТЕЛЬ");
EnumRole(String descriptin)
{
this.descriptin=descriptin;
}
public String getDescription()
{
return this.descriptin;
}
private String descriptin;
}
You need to create any Spring bean with #PostConstruct annotated method and for example create transaction using PlatformTransactionManager:
#Service
public class AnyBean {
#Autowired
private PlatformTransactionManager transactionManager;
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
private void init() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallback<Object>() {
#Override
public Object doInTransaction(TransactionStatus transactionStatus) {
// HERE YOU CODE
for(EnumRole role:EnumRole.values())
{
Role r = getRoleByName(role);
if(r==null)
{
r=new Role();
r.setName(role);
r.setDescription(role.getDescription());
sessionFactory.getCurrentSession().save(r);
}
}
return null;
}
});
}
}
I hope this helped.
Create a service with method insertRoles() marked with #Transactional and call service.insertRoles().
Or, more easy:
create a transaction
add roles
commit (or manage rollback if error occured)
Do this stuff after your application startup has been completed

Categories

Resources