1

I got two Entities in my Spring Boot Application:

User:

public class User implements java.io.Serializable, UserDetails  {
@GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "person") )
@Id
@GeneratedValue(generator = "generator")

@Column(name = "person_id", unique = true, nullable = false)
public int getPersonId() {
    return this.personId;
}

public void setPersonId(int personId) {
    this.personId = personId;
}

@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
public Person getPerson() {
    return this.person;
}

public void setPerson(Person person) {
    this.person = person;
}

    @Column(name = "email", unique = true, nullable = false, length = 50)
public String getEmail() {
    return this.email;
}

public void setLoginVersuche(int loginVersuche) {
    this.loginVersuche = loginVersuche;
}

@Column(name ="loginVersuche")
public int getLoginVersuche() {
    return loginVersuche;
}
...omitted code...

}

and Person:

public class Person implements java.io.Serializable {

@Id
@GeneratedValue(strategy = IDENTITY)

@Column(name = "idPerson", unique = true, nullable = false)
public Integer getIdPerson() {
    return this.idPerson;
}

public void setIdPerson(Integer idPerson) {
    this.idPerson = idPerson;
}

@OneToOne(fetch = FetchType.LAZY, mappedBy = "person")
public User getUser() {
    return this.user;
}

public void setUser(User user) {
    this.user = user;
}
...omitted code...
}

and I have my DAO:

@Repository
public interface UserDAO extends CrudRepository<User, Integer>{
    User findByEmail(String user);
}

Now I want to update a field in authentication. For this I ask for the User object (email is unique) and set the field. After this I want to update the object in db:

User userByDB = userDAO.findByEmail(username);    
userByDB.setLoginVersuche(userByDB.getLoginVersuche()+1);
userDAO.save(userByDB);

It just doesn't update the datafield in db, but I also get no error. Does it have to do with my primary key in user that is a foreign key?

UPDATE-1

@Transactional
    @Modifying
    @Query("update User u set u.loginVersuche = u.loginVersuche+1, u.gesperrt = ?1 where u.email = ?2")
    public int incrementLoginVersuche(boolean gesperrt, String email);

UPDATE-2

23:51:43.181 TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization 23:51:43.182 TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.incrementLoginVersuche] 23:51:43.183 DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Opening JPA EntityManager 23:51:43.184 TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@71dfe394 23:51:43.184 TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14569591031 23:51:43.185 TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO 23:51:43.186 TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL 23:51:43.189 DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Registering transaction synchronization for JPA EntityManager 23:51:43.207 TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.orm.jpa.EntityManagerHolder@1028e769] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3d018d7d] to thread [http-nio-8443-exec-2] 23:51:43.208 TRACE org.hibernate.engine.query.spi.QueryPlanCache - Located HQL query plan in cache (update User u set u.loginVersuche = u.loginVersuche+1, u.gesperrt = ?1 where u.email = ?2) 23:51:43.210 TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@1028e769] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3d018d7d] bound to thread [http-nio-8443-exec-2] 23:51:43.214 DEBUG org.hibernate.jpa.spi.AbstractEntityManagerImpl - Mark transaction for rollback 23:51:43.215 TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.incrementLoginVersuche] after exception: javax.persistence.TransactionRequiredException: Executing an update/delete query 23:51:43.217 TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on javax.persistence.TransactionRequiredException: Executing an update/delete query

UPDATE-3

TransactionManager configuration:

@Bean(name = "transactionManager")
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }
AndreasGloeckner
  • 292
  • 3
  • 5
  • 18

3 Answers3

1

According to docs (4.6. Transactionality)

CRUD methods on repository instances are transactional by default

so you don't need @Transactional(readOnly = true) annotation above your UserDAO interface. Instead, you should make your saving method transactional (assuming that your class is a valid Spring Component):

@Transactional
public void incrementVersuche() {
    ...    
    User userByDB = userDAO.findByEmail(username);    
    userByDB.setLoginVersuche(userByDB.getLoginVersuche()+1);
    userDAO.save(userByDB);
}

UPDATE

public interface IUserSaver { 
        void incrementVersucheAndSave(String username)
}

@Service
public class UserSaver implements IUserSaver {

@Transactional
public void incrementVersucheAndSave(String username) {
        User userByDB = userDAO.findByEmail(username);    
        userByDB.setLoginVersuche(userByDB.getLoginVersuche()+1);
        userDAO.save(userByDB);
      }
}

And then in your AuthProvider:

....
@Autowired
private IUserSaver saver;

 public void incrementLoginVersuche() {
   ....
   saver.incrementVersucheAndSave(username);
}
Enigo
  • 3,685
  • 5
  • 29
  • 54
  • Ok, I implemented my own query (see updated answer), but this won't work aswell. All annotations came from spring packages. Getting error: `Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query` **But more important:** Why does my first try not work? Would like to understand that. – AndreasGloeckner Mar 02 '16 at 21:11
  • @Andy please, share your configuration and service where you call `incrementLoginVersuche` method. In your configs you should have something like ``. Do you have that? Also note, that you don't need @Repository annotation for you repository, since it extends `CrudRepository`. And last, since you are not using native query I think that you should write your query like `@Query("update User u set u.loginVersuche = u.loginVersuche+1, u.gesperrt = :gesperrt where u.email = :email")` – Enigo Mar 03 '16 at 06:58
  • See UPDATE-3, I am using Java config not namespaces. On top of that I updated my query as you said: `@Query("update User u set u.loginVersuche = u.loginVersuche+1, u.gesperrt = :gesperrt where u.email = :email") public int incrementLoginVersuche(@Param("gesperrt") boolean gesperrt, @Param("email")String email);` Maybe have a look at @TheBitman. – AndreasGloeckner Mar 03 '16 at 09:40
  • Do you have `@EnableTransactionManagement` annotation above your configs? Also, (just to be double sure) check that all of your annotations come from spring. – Enigo Mar 03 '16 at 11:05
  • Yeah, I did in my AppConfig and all annotations come from spring. – AndreasGloeckner Mar 03 '16 at 11:08
  • Hmm, ok, just for testing try to create new Spring Service with first version of your code, make that method Transactional and add that service to your AuthProvider. See updated answer – Enigo Mar 03 '16 at 12:23
  • Ok, I changed as you told me, though it seems to be a bit weired (don't get it what this change will do). **But**, my object won't update. The console output says following: `22:09:28.067 TRACE org.hibernate.event.internal.AbstractSaveEventListener - Persistent instance of: com.spring.model.User 22:09:28.068 TRACE org.hibernate.event.internal.DefaultMergeEventListener - Ignoring persistent instance` All attributes are filled. – AndreasGloeckner Mar 04 '16 at 21:21
  • Just wanted to make sure that the problem not in Spring AuthProvider. Anyway, it's good practice to separate code into different services. Have your situation changed? Cos for now I'm out of ideas. – Enigo Mar 09 '16 at 07:42
  • Actually yes. Since yesterday evening, I found a solution. I had another problem with just updating my last login timestamp. It doesn't work, too. After talking to a colleague of mine, he told me that he never used a TransactionManager as I did. `@Bean(name = "transactionManager") public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); }` That was my transaction Manager. **I thought you always need one, don't you?!** After deleting it my transactions went fine! Updating "LoginVersuche" and last login timestamp. **Do you have an explanation?** – AndreasGloeckner Mar 09 '16 at 08:04
  • The only possible explanation I can think of is that Spring Data has own transaction manager by default. In project that I work for it was enabled (as far as I understand) by `@EnableJpaRepositories` annotation. BTW, you should share [this link](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html) to your colleague :) – Enigo Mar 09 '16 at 09:40
0

Remove

@Transactional(readOnly = true)

from your repository. The transaction will not throw an exception: @Transactional readOnly

0

The method called without a transaction. Check your tr demercations or the caller env. is not transactional.the original problem : if you set the tr RO, it won't execute update SQL commands.

The Bitman
  • 1,279
  • 1
  • 11
  • 25
  • Ok, pls have a look at the comment i did to @Enigo. I am calling now "incrementLoginVersuche" (see UPDATE-1), but now getting `Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query` @Transactional is set as u can see. I am calling the method by the `authenticate`-method of my Authentication-Provider. – AndreasGloeckner Mar 02 '16 at 21:32
  • That is not change the fact: you called the method without a tr. Maybee the stack trace could be a goog starting point – The Bitman Mar 02 '16 at 21:41
  • Did I missunderstand something? I have my customAuthenticationProvider with a overriding method: `@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {...}` In this function, I call `incrementLoginVersuche` which is marked as Transactional. So it should start a new transaction, shouldn't it? – AndreasGloeckner Mar 02 '16 at 21:50
  • The cause of the tr-less could be in the environment. Bad settings, missing connection, etc. Stack trace may reveal it :) – The Bitman Mar 02 '16 at 21:56
  • Nevertheless, why can't I read my user object, set a new value and save it then? Is it all about the transaction? I would like to annotate the `authenticate` with `@Transactional`, but I can't because its overridding. The `authenticate`method is invoked through spring security by sending post to with ajax to /login. – AndreasGloeckner Mar 02 '16 at 21:57
  • If you set the method to transactional : no transaction, no DB command execution – The Bitman Mar 02 '16 at 22:00
  • What? I don't get it. I set the DAO-Method to @Transactional (see update-1). The bigger problem may is that I can't set the authenticate-method cause it's an overriding one. But it should start a Transaction when I set it to the DAO? – AndreasGloeckner Mar 02 '16 at 22:07
  • I updated my answer with hopefully the relevant logpart. You seem to be right, but still don't know how to annotate an `@Override`-method with `@Transactional`. – AndreasGloeckner Mar 02 '16 at 23:01
  • The @Override annotation expected just when you implement an interface method. Other cases it is optional.It looks very similar at first glance. Maybee helps. http://stackoverflow.com/questions/32197225/overriding-transactional-annotation-spring-hibernate – The Bitman Mar 03 '16 at 08:32
  • I implement `AuthenticationProvider` from SpringSecurity, so I have to use `@Override` to override the authenticate method. – AndreasGloeckner Mar 03 '16 at 09:21