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.
Related
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;
}
}
This question already has answers here:
Spring and hibernate: No Session found for current thread
(6 answers)
Closed 4 years ago.
I'm getting this error when I try to upload a picture in my project. The project executes fine until it has to effectively upload the picture to the database (I'm using postgresql), but this last step never works.
The following code was updated having considered the answers below.
Here's my controller (a part of it):
#Autowired
private FileUploadImpl fileUploadImpl;
...
#RequestMapping(value = "publish4" ,method = RequestMethod.POST)
public ModelAndView publish4(#Valid #ModelAttribute("fourthPublicationForm") final FourthPublicationForm form, final BindingResult errors,
#RequestParam("type") String type, #RequestParam("operation") String operation , #RequestParam CommonsMultipartFile[] fileUpload) {
if (errors.hasErrors()) {
//return helloPublish3(form,operation,type);
}
System.out.println("operation: "+ operation);
System.out.println("type: "+ type);
ps.create(form.getTitle(), form.getAddress(), operation, form.getPrice(), form.getDescription(),
type, form.getBedrooms(), form.getBathrooms(), form.getFloorSize(), form.getParking());
if (fileUpload != null && fileUpload.length > 0) {
for (CommonsMultipartFile aFile : fileUpload){
System.out.println("Saving file: " + aFile.getOriginalFilename());
UploadFile uploadFile = new UploadFile();
uploadFile.setAddress(form.getAddress());
uploadFile.setData(aFile.getBytes());
fileUploadImpl.save(uploadFile);
}
}
return new ModelAndView("redirect:/hello/home");
}
This is fileUploadDao in interface:
public interface FileUploadDao {
void save(UploadFile uploadFile);
}
This is in services:
#Service
public class FileUploadImpl {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadImpl() {
}
#Transactional
public void save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
}
THe following in persistence:
#Repository
public class FileUploadDAOImpl implements FileUploadDao {
#Autowired
private SessionFactory sessionFactory;
public FileUploadDAOImpl() {
}
public FileUploadDAOImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void save(UploadFile uploadFile) {
sessionFactory.getCurrentSession().save(uploadFile);
}
}
I got this in WebConfig.java (among other stuff)
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] { "ar.edu.itba.paw" }
);
//sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Autowired
#Bean(name = "fileUploadDao")
public FileUploadDao getUserDao(SessionFactory sessionFactory) {
return new FileUploadDAOImpl(sessionFactory);
}
#Bean(name = "multipartResolver")
public CommonsMultipartResolver getCommonsMultipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(20971520); // 20MB
multipartResolver.setMaxInMemorySize(1048576); // 1MB
return multipartResolver;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
A little bit more of the error:
org.hibernate.HibernateException: No Session found for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:106)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
at ar.edu.itba.paw.persistence.FileUploadDAOImpl.save(FileUploadDAOImpl.java:25)
at ar.edu.itba.paw.webapp.controller.HelloWorldController.publish4(HelloWorldController.java:260)
I've seen other questions where the answer was the lack of use of "transactional". I'm using that annotation here, but I'm not sure if the way it's 100% correct.
First remove #Transactional from FileUploadDAOImpl.
Change base package accordingly,
sessionFactory.setPackagesToScan(
new String[] { "base.package.to.scan" }
);
base.package.to.scan seems like invalid base package naming, change it to ar.edu.itba.paw.
You need a transaction manager to get use of #Transactional. Add it to WebConfig
#Bean
#Autowired
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory());
return txManager;
}
This might get this code work, give it a try.
UPDATE: Also make sure following annotations present on WebConfig class,
#Configuration
#ComponentScan({"ar.edu.itba.paw"})
#EnableTransactionManagement(mode = AdviceMode.PROXY)
public class WebConfig {
// code
}
As you said from the first place, you have confused the actual layers. Still you could make it work properly in your situation but lets discuss a bit your implementation.
FileUploadDao is it a DAO or is it a Service ?
FileUploadImpl seems that you're confusing #Service with #Repository ,
maybe reading this out might help you. Spring Data Repositories , Spring Service Annotation
You ve made a transactional method , save in which i cannot say what you want to achieve exactly. You are also autowiring both FileUploadDao and SessionFactory, although you want to implement the first and inside the method you are trying to persist the object twice by first calling save upon the repository (thats a StackOverflowError from the first place, but you are lucky because Spring knows what to autowire) and then you are trying to call save a second time upon the Hibernate's SessionFactory , which breaks the abstract JPA contract. Also if you noticed , the error at the logs you posted , comes from the second save.
#Transactional not going to discuss how is this working , as you haven't posted your whole app-config. But again , you could read this for more info.
So based on the examples you shared , i am going to prepare 2 cases which might help you understand whats going on underneath.
First Case , Spring DATA , not really care if its Hibernate or another JPA provider underneath.
Your FileUploadImpl Becomes : FileUploadService
#Service
public class FileUploadService {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadService() {
}
#Transactional
public void save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
}
Inside your controller , you are Autowiring the Service (layer) not directly the Repository/DAO(layer). There is not anything that stops you tho , its just a matter of design(if you still not get that point, raise another question).
A part of your part's Controller
#Autowired
private FileUploadService fileUploadService;
#RequestMapping(value = "publish4" ,method = RequestMethod.POST)
public ModelAndView publish4(#Valid #ModelAttribute("fourthPublicationForm") final FourthPublicationForm form, final BindingResult errors,
#RequestParam("type") String type, #RequestParam("operation") String operation , #RequestParam CommonsMultipartFile[] fileUpload) {
.........
fileUploadService.save(uploadFile);
}
Second Case , if you really want to use hibernate goodies , then there is not any reason autowiring the Repository , but simply implement those calls by yourself.
import org.springframework.stereotype.Component;
#Component
public class FileUploadDao {
#Autowired
private SessionFactory sessionFactory;
public FileUpload save(FileUpload obj) {
return sessionFactory.getCurrentSession().save(obj);
}
public FileUpload merge(FileUpload obj) {
return sessionFactory.getCurrentSession().merge(obj);
}
..... delete / update / or custom queries(SQL/JPQL/HQL) can be placed here
}
Your service simply exposes those methods , check the difference , i am applying the #Transactional annotation on this layer(ofc again you can put it in the DAO layer, but as i said its a matter of design).
#Service
public class FileUploadService {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadService() {
}
#Transactional
public UploadFile save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
#Transactional
public UploadFile merge(UploadFile uploadFile) {
fileUploadDao.merge(uploadFile);
}
....rest of the methods you want to expose , or combinations of mulitple DAOs
}
Your controller remains the same , and thats the actual reason you need to have layers.
I am trying to update a node on Neo4J, but what ends up happening is that it creates a duplicate Node. I read that the update has to be in a single transaction and I added #Transactional, but still same result. Here is what I have. I tried the approach of reading and deleting the old node, and saving the new one and it appears to be working. But, I think that is not the right approach. Why the #Transactional annotation not working. Thank you.
#EnableNeo4JRepositories(com.example.graph.repo)
#EnableTransactionManagement
#org.springframework.contect.annotation.Configuration
public class Neo4JConfig {
#Bean
public Configuration configuration() {
Configuration cfg = new Configuration();
cfg.driverConfiguration()
.setDriverClassName("org.neo4j.ogm.drivers.http.driver.HttpDriver")
.setURI("http://neo4j:neo4j#localhost:7474");
return cfg;
}
#Bean
public SessionFactory sessionFactory() {
return new SessionFactory(configuration(), "com.example");
}
#Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4JTransactionManager(sessionFactory());
}
}
#Service
public class UserService{
#Autowired
UserRepository userRepository;
#Transactional
public void updateUser(User user) {
User existingUser = userRepository.getExistingUser(user.getUserName());
if(existingUser != null ) {
user.setSomeValue(existingUser.getSomeValue());
userRepository.save(user);
}
}
}
Spring AOP uses JDK Proxy mechanism by default. It means that you must invoke #Transactional method via interface method.
So you should split your service into interface UserService and implementation (say UserServiceImpl), autowire the interface into the code where you currently autowire the impementation, and then invoke transactional method via interface.
P.S. Another approach is to force Spring to use CGLIB as long as this mechanism is not limited to interfaces. More details for both mechanisms https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html
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 am stuck with null values in an autowired property. I am hoping I could get some help.
We are using for the project spring-boot version 0.5.0.M6.
The four configuration files with beans are in one package and are sorted by "area":
Data source configuration
Global method security configuration (as we use Spring-ACL)
MVC configuration
Spring Security configuration
The main method that bootstraps everything is in the following file:
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableAutoConfiguration(exclude = {
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
SecurityAutoConfiguration.class,
ThymeleafAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
MessageSourceAutoConfiguration.class,
WebSocketAutoConfiguration.class
})
#Configuration
#ComponentScan
public class IntegrationsImcApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(
IntegrationsImcApplication.c lass, args);
}
}
The first file that holds the data source configuration beans is as follows (I have omitted some method body parts to make it more readable):
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
#Configuration
public class RootDataSourceConfig
extends TomcatDataSourceConfiguration
implements TransactionManagementConfigurer {
#Override
public DataSource dataSource() {
return jpaDataSource();
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return jpaTransactionManager();
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean(name="jpaDataSource")
public DataSource jpaDataSource() {......}
#Bean(name = {"transactionManager","txMgr"})
public JpaTransactionManager jpaTransactionManager() {......}
#Bean(name = "entityManagerFactory")
public EntityManagerFactory jpaEmf() {......}
}
And here is the next configuration file, that depends on the data source from above. It has about 20 beans related to ACL configuration, but it fails on the firsts bean that uses data source:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class RootGlobalMethodSecurityConfig
extends GlobalMethodSecurityConfiguration
implements Ordered {
#Autowired
public DataSource dataSource;
#Override
public int getOrder() {
return IntegrationsImcApplication.ROOT_METHOD_SECURITY_CO NFIG_ORDER;
}
#Bean
public MutableAclService aclService()
throws CacheException, IOException {
MutableJdbcAclService aclService = new MutableJdbcAclService(
dataSource, aclLookupStrategy(), aclCache());
aclService.setClassIdentityQuery("SELECT ##IDENTITY");
aclService.setSidIdentityQuery("SELECT ##IDENTITY");
return aclService;
}
...................................
}
Basically invoking aclService() throws an error as dataSource is null. We have tried ordering the configuration files by implementing the Ordered interface. We also tried using #AutoConfigureAfter(RootDataSourceConfig.class) but this did not help either. Instead of doing #Autowired on the DataSource we also tried injecting the RootDataSourceConfig class itself, but it was still null. We tried using #DependsOn and #Ordered on those beans but again no success. It seems like nothing can be injected into this configuration.
The console output at the startup is listing the beans in the order we want them, with data source being the first. We are pretty much blocked by this.
Is there anything weird or unique we are doing here that is not working? If this is as designed, then how could we inject data source differently?
Repo: github
Eager initialization of a bean that depends on a DataSource is definitely the problem. The root cause is nothing to do with Spring Boot or autoconfiguration, but rather plain old-fashioned chicken and egg - method security is applied via an aspect which is wrapped around your business beans by a BeanPostProcessor. A bean can only be post processed by something that is initialized very early. In this case it is too early to have the DataSource injected (actually the #Configuration class that needs the DataSource is instantiated too early to be wrapped properly in the #Configuration processing machinery, so it cannot be autowired). My proposal (which only gets you to the same point with the missing AuthenticationManager) is to declare the GlobalMethodSecurityConfiguration as a nested class instead of the one that the DataSource is needed in:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
protected static class ActualMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Autowired
#Qualifier("aclDaoAuthenticationProvider")
private AuthenticationProvider aclDaoAuthenticationProvider;
#Autowired
#Qualifier("aclAnonymousAuthenticationProvider")
private AnonymousAuthenticationProvider aclAnonymousAuthenticationProvider;
#Autowired
#Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(aclDaoAuthenticationProvider);
auth.authenticationProvider(aclAnonymousAuthenticationProvider);
}
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
i.e. stick that inside the RootMethodSecurityConfiguration and remove the #EnableGlobalMethodSecurity annotation from that class.
I might have resolved the problem.
GlobalMethodSecurityConfiguration.class has the following setter that tries to autowire permission evaluators:
#Autowired(required = false)
public void setPermissionEvaluator(List<PermissionEvaluator> permissionEvaluators) {
....
}
And in my case the aclPermissionEvaluator() bean needs aclService() bean, which in turn depends on another autowired property: dataSource. Which seems not to be autowired yet.
To fix this I implemented BeanFactoryAware and get dataSource from beanFactory instead:
public class RootMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration implements BeanFactoryAware {
private DataSource dataSource;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.dataSource = beanFactory.getBean("dataSource", DataSource.class);
}
....
}
After this, other exception showed up, whereSecurityAutoConfiguration.class is complaining about missing AuthenticationManager, so I just excluded it from #EnableAutoConfiguration. I am not sure if its ideal, but I have custom security configuration, so this way everything works ok.