I have a Spring MVC application we're developing, using Spring 4.2.3 and Hibernate 5.0.6. In some of our testing we found that some of our data is not being saved to the database (Oracle). The code is as follows:
#Service
#Transactional
public class MyService {
#Autowired
private MyDao dao;
public void saveCustomer(Customer c) {
dao.saveCustomer(c);
}
}
#Repository
#Transactional
public class MyDao {
#Autowired
private SessionFactory sessionFactory; // Hibernate's SessionFactory
public void saveCustomer(Customer cust) {
sessionFactory.getCurrentSession().clear();
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().save(cust);
}
}
To be honest, I just saw that #Transactional on MyDao, as I'm writing this. That probably is wrong, since #Transactional is already on the service layer.
We're using Java Config....showing the relevant beans....
#Configuration
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan({"my.package"})
public class Config extends WebMvcConfigurerAdapter {
#Bean(name = "sessionFactory")
public SessionFactory getSessionFactory(ComboPooledDataSource dataSource) {
LocalSessionFactoryBuilder sessionBuilder = new LocalSessionFactoryBuilder(dataSource);
sessionBuilder.scanPackages("my.entities");
sessionBuilder.addProperties(getHibernateProperties());
return sessionBuilder.buildSessionFactory();
}
#Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager transactionManager = new HibernateTransactionManager(sessionFactory);
return transactionManager;
}
}
My question is, other than the #Transactional on MyDao, which shouldn't be there, is there anything else that stands out to anyone as to why Customer does not save to the database? Are we even doing transactions correctly? I've never liked using Hibernate's SessionFactory, but that's the way we went. Are we using it correctly within a Spring Transaction?
Thanks!
Chris
Related
I am working on a Spring-MVC application in which I have 2 data-sources defined for different types of tasks. Currently, during migration from XML to Java, I stumbled upon a requirement to add a new HibernateTransactionManager object for #Transactional to work. For the 2 different data-sources, I have 2 separate SessionFactory instances. But when I try to create yet another instance of HibernateTransactionManager with secondary data-source, I get a non-unique exception.
Do I require a secondary HibernateTransactionManager instance for the config I am posting or will 1 suffice? If required, how can I create one? Thank you.
Error log :
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: primary_tx,extended_tx
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:368)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:271)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springfram
WebConfig.java :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.ourapp.spring"})
#EnableTransactionManagement
#EnableCaching
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
#Bean(name="extended_tx")
public HibernateTransactionManager txName() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(getExtendedSessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("sessionFactory_origin")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig().primaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
#Bean
#Qualifier("sessionFactory_extended")
public LocalSessionFactoryBean getExtendedSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig_Extended().secondaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
}
Typical Service Layer method :
#Service
#Transactional("primary_tx")
public class ChatRoomMembersServiceImpl implements ChatRoomMembersService{
private final ChatRoomMembersDAO chatRoomMembersDAO;
#Autowired
public ChatRoomMembersServiceImpl(ChatRoomMembersDAO chatRoomMembersDAO){
this.chatRoomMembersDAO = chatRoomMembersDAO;
}
}
Typical DAO layer method :
#Repository
#Transactional("primary_tx")
public class ChatRoomMembersDAOImpl implements ChatRoomMembersDAO{
#Autowired
#Qualifier(value = "sessionFactory_origin")
private SessionFactory sessionFactory;
#Autowired
#Qualifier(value = "sessionFactory_extended")
private SessionFactory sessionFactory_extended;
}
Now, whenever required, I am referring to the extended SessionFactory instance. Right now, I have annotated DAO layer methods requiring extended sessionFactory with secondary_tx, but it's not working. Thank you. :-)
The answer to your question
Do I require to define separate TransactionManager for 2 data sources?
is YES
Java8 onwards you can have same annotation(provided that the annotation is marked #Repeatable) multiple times on a method. Other way to accomplish this is to gave a custom annotation which takes care of one of the datasources.
The exception you are getting is because there are two qualifying beans for transation management. You need a Qualifier for this.
Instead of:
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Use
#Bean #Qualifier("primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Now, Let say You want to make the transactions in my method m1 as atomic. The following is what you need
#Transactional("primary_tx")
public void m1(){
}
I am using Spring Boot for my application. I am defining JNDI name in the application.properties file.
When I am trying to get JdbcTemplate in below class, its null:
#Configuration
public class DemoClass
{
#Autowired
private JdbcTemplate template;
#Bean
private DataSource getDS(){
return template.getDataSource(); //NPE
}
}
Another Class
#Component
public class SecondClass {
#Autowired
private JdbcTemplate template;
public void show(){
template.getDataSource(): // Working Fine
}
}
I am not sure this configured by default.. In case it is not, then maybe you can try configuring it yourself:
#Autowired
DataSoure dataSource;
#Bean
public JdbcTemplate getJdbcTemplate() {
return new JdbcTemplate(dataSource);
}
in any case if you need only the DataSource, I think it is auto-configured by Spring Boot so you can autowire it directly when you need it.
#Repository
public class DataRepository {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int updateCandidate() {
return this.jdbcTemplate.update("update .... from table where ....");
}
}
application.properties
database connection details
spring.datasource.url=jdbc:oracle:thin:***
spring.datasource.username=Scott
spring.datasource.password=Tiger
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.tomcat.initial-size=1
spring.datasource.tomcat.max-active=1
spring.datasource.tomcat.min-idle=1
spring.datasource.tomcat.max-idle=1
If you're getting a NPE at getDS. This means JdbcTemplate hasn't been injected yet, maybe it couldn't be injected.
Give spring a hint at bean dependencies by
#Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource)
}
Or
#Bean
#DependsOn("template")
public DataSouce getDS(){
return template.getDataSource();
}
By default #Autowired sets required=true so the DemoClass should not be constructed by Spring.
Most likely you are creating new DemoClass() or disabled the annotation config altogether and the DemoClass class is registered manually e.g. using XML.
Instead ensure that the DemoClass class is discovered using Spring's component scan e.g. using #SpringBootApplication or #ComponentScan e.g. as per this example.
I have a DB access module that interacts with multiple types of data sources.
Some higher modules use the DB module without interacting with JDBC data source. Therefore, I would like the data source to be defined as lazy. However, I also define a transaction manager which I can't define as lazy since it is not a direct dependency in my code. Since the transaction manager depends on the data source, the latter is always created.
#Configuration
#EnableTransactionManagement
public class SpringConfig {
#Bean(destroyMethod = "close")
#Lazy
#Autowired
public DataSource dataSource() throws Exception {
//create data source
}
#Bean
#Lazy
#Autowired
public PlatformTransactionManager txManager() throws Exception {
DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource());
return txManager;
}
}
#Lazy
#Repository("FundRepository")
public class MyRepository {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
}
Any idea how to make this work?
I have this DB configuration:
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = "com.mycompany")
public class DBConfiguration {
#Bean(destroyMethod = "close")
public javax.sql.DataSource dataSource() {
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/v2");
ds.setUsername("java");
ds.setPassword("mypass");
ds.setInitialSize(5);
ds.setMaxActive(10);
ds.setMaxIdle(5);
ds.setMinIdle(2);
ds.setRemoveAbandoned(true);
ds.setLogAbandoned(true);
return ds;
}
#Bean
public DataSourceTransactionManager txManager()
{
DataSourceTransactionManager tx= new DataSourceTransactionManager(dataSource());
return tx;
}
}
QUESTION UPDATED
I have some trouble to understand how #Transaction annotation works, please consider this scenario:
#Service
public class FirstService {
#Transactional //<--- this annotation seems to be mandatory for my rollback but I don't want it.
public void test() throws Exception{
secondService.insert();
}
}
#Service
public class SecondService {
#Transactional //<-- I would like to have only this method in transaction
protected void insert() throws Exception{
dao.insertEntity(new Entity()); //<<--- this is an SQL insert command
throw new RuntimeException("Rollback test");
}
}
Test code:
#RequestMapping("/test") #ResponseBody
public void test() throws Exception{
firstService.test();
}
Dao:
public void insertEntity(Entity e) {
getJdbcTemplate().update(SQL_INSERT,e.getCode(),e.getName());
}
This test WORKS, thrown exception could rollback the transaction.
Why if I omit the #Transaction annotation on the firstService there is not rollback?
Seems that from #Controller to #Service the txmanager looks for #Transaction annotation, from #Service to (another) #Service or #Component it doesn't look for it.
This works for me:
#Bean(name = "transactionManager")
#Primary
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
Please update the spring version and maybe some logs or debug logs and see if there is an issue with Transaction
There were two error.
First error was that I cannot nest two method (first not transactional, second transactional) in the same object then I have to separate it in two objects (as you can see in updated question).
Second error was that #Transaction annotation shuold be applied to public methods not to private or protected.
I've read I believe tried all of the posts on this, but no luck in finding the right answer.
I am using java based configuration with my spring mvc project, and wanted to try Spring CrudRepository, to get away from DAOs, and that is when the whole hell broke loose:
started with "no transaction is in progress" on flush after persist:
- tried adding #Transactional to the method - none of the variations found here worked
- tried changing configuration, but since it is java based, most of the answers are xml based. no luck either.
So finally I have to ask:
How to configure my project to make CrudRepository persist, or how to create Spring EntityManager using java configuration.
This is the last version of my configuration file:
#Configuration
#ComponentScan(basePackages = { "ba.fit.vms" })
#ImportResource(value = "classpath:spring-security-context.xml")
#EnableTransactionManagement
#EnableJpaRepositories
public class AppConfig {
#Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("/persistence.properties"));
return ppc;
}
// Security Configuration
#Bean
public KorisnickiServis korisnickiServis(){
return new KorisnickiServis();
}
#Bean
public TokenBasedRememberMeServices rememberMeServices() {
return new TokenBasedRememberMeServices("remember-me-key", korisnickiServis());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
// Jpa Configuration
#Value("${dataSource.driverClassName}")
private String driver;
#Value("${dataSource.url}")
private String url;
#Value("${dataSource.username}")
private String username;
#Value("${dataSource.password}")
private String password;
#Value("${hibernate.dialect}")
private String dialect;
#Value("${hibernate.hbm2ddl.auto}")
private String hbm2ddlAuto;
#Bean
public DataSource configureDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(configureDataSource());
entityManagerFactoryBean.setPackagesToScan("ba.fit.vms");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, dialect);
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto);
//jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL, true);
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
#Bean
public PlatformTransactionManager transactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(configureEntityManagerFactory().getObject());
return transactionManager;
}
}
I've tried number of variations, but was always receiving same "no transaction is in progress" error.
Also, just a glimpse at the repos:
LokacijaRepository:
#Transactional
public interface LokacijaRepository extends CrudRepository<Lokacija, Long> {
}
And LokacijaRepositoryImpl:
#Repository
public class LokacijaRepositoryImpl implements LokacijaRepository {
protected static Logger logger = Logger.getLogger("repo");
#PersistenceContext // tried this as well(type= PersistenceContextType.EXTENDED)
private EntityManager entityManager;
#Override
#Transactional// tried number of variations here as well, like REQUIRED...
public <S extends Lokacija> S save(S entity) {
logger.debug("trying to save!");
try {
entityManager.persist(entity);
entityManager.flush();
return entity;
} catch (Exception e) {
logger.debug("error: "+ e.toString());
return null;
}
}
If you need anything else to help me figure this one out, let me know.
The problem is that you are attempting to create an implementation of LokacijaRepository (in LokacijaRepositoryImpl) while Spring Data JPA (which you have configured) is trying to do the same.
What you need to do is:
totally remove LokacijaRepositoryImpl
Either change configureEntityManagerFactory to entityManagerFactory or add entityManagerFactoryRef=configureEntityManagerFactory to #EnableJpaRepositories