I am trying the Active Record pattern using Spring and Hibernate framework. Below is the description of this pattern:
An object carries both data and behavior. Much of this data is persistent and needs to be stored in a database. Active Record uses the most obvious approach, putting data access logic in the domain object. This way all people know how to read and write their data to and from the database.
So, I removed the traditional Service class and moved its logic and the #Transactional annotation to the entity class. But when I run my application again, the following exception was thrown.
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:133)
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:454)
weibo.datasource.UserDao.save(UserDao.java:17)
weibo.domain.User.register(User.java:32)
weibo.web.UserController.register(UserController.java:29)
Source Code
The UserController class:
#PostMapping("/users/register")
public String register(#RequestParam("username") String username,
#RequestParam("password") String password) {
User user = new User(userDao, username, password);
user.register();
return "redirect:/users/login";
}
The User entity class:
#Entity
#Table(name="USERS")
#Transactional
public class User {
#Id
#GeneratedValue
private int id;
private String name;
private String password;
#Transient
private UserDao userDao;
public User() {}
public User(UserDao userDao, String username, String password) {
...
}
public void register() {
userDao.save(this);
}
}
The UserDao class. No #Transactional annotated.
public UserDao(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void save(User user) {
sessionFactory.getCurrentSession().save(user);
}
Why?
UPDATE
As #cristianhh said, the #Transactional annotation must be used in a Spring-Managed bean. However, the entity class is not.
No, while #Transactional is managed by Spring, #Entity is managed by Hibernate.
Hibernate beans are not managed by Spring and respectively not wrappable by the #Transactional annotation.
You can however, use #Transactional in the service/repository layer and wrap a function sending the entity's data access object (DAO).
Related
Please, look at the two code examples bellow which I'm going to use in my Spring Boot project. They both do merely the same thing - add a new object into users table, represented by User entity with username defined as #Id and a unique constraint imposed on email column (there are some other columns as well, but they are not shown here for brevity). Note: I can't simply use save() method from CrudRepository, because it merges existing record with new object if they both have the same username value. Instead, I need to insert a new object with appropriate exception thrown for duplicate data persistence.
My question is about which option should be given a favor. With EntityManager, I don't need to construct SQL statement. Apart from that obvious observation, are there any advantages which one method may offer over the other (especially, in the matter of performance and resources consumption)?
Also, when I read latest books and tutorials about data persistence in Spring Boot, they mainly focus on Spring Data JPA. For example, the 5th edition of "Spring in Action" has no word about Hibernate's EntityMnager. Does it mean that dealing with Hibernate directly can be regarded as kind of "old school" and should generally be avoided in modern projects?
Option #1: Hibernate's EntityManager
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
#Transactional
public class RegistrationController {
#PersistenceContext
EntityManager entityManager;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
entityManager.persist(newUser);
entityManager.flush();
} catch (PersistenceException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, messsage);
}
return Collections.singletonMap("message", "Success...");
}
}
Option #2: custom #Query from CrudRepository
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
public class RegistrationController {
private final UsersRepository usersRepository;
#Autowired
public RegistrationController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
usersRepository.insert(newUser);
} catch (DataIntegrityViolationException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, message);
}
return Collections.singletonMap("message", "Success...");
}
}
public interface UsersRepository extends CrudRepository<User, String> {
#Modifying
#Transactional
#Query(nativeQuery = true, value = "INSERT INTO users (username, email) " +
"VALUES (:#{#user.username}, :#{#user.email})")
void insert(#Param("user") User newUser);
}
See this answer for Using JPA repository vs Entity Manager.
Best practice is to not use Repository directly. use Service layer between controller and repository where you can implement the logic for duplicate entries by checking if the record already exist in DB using findByUsername(String username); throw exception if it already exist else save() the object in DB
With the given requirements, the username filed in the entity never qualifies for the #Id.
Why can't u add an explicit id field with some sequence generator for the id filed and just keep the username marked with unique constraint only.
The problem is that one day we discovered that if we're saving an object in spring boot repository, another objects that are changed in the same method are also updated and persisted in the database.
The curiosity is massive to find out why does this actually happen. I created sample project using Spring Initializr and some template code to show the actual situation (tried to keep the number of dependencies as low as possible).
Using Spring boot version 1.5.11 (SNAPSHOT) and project has following dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.mariadb.jdbc:mariadb-java-client:2.1.0')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Now to the point:
Project has two entities, Pet:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Pet.class)
public class Pet {
#Id
#GeneratedValue
private long id;
private String type;
public Pet() {}
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
and User:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = User.class)
public class User {
#Id
#GeneratedValue
private long id;
private String name;
public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Both entities also have repositories, Pet:
#Repository
public interface PetRepository extends CrudRepository<Pet, Long> {
Pet findPetById(Long id);
}
User:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findUserById(Long id);
}
And one simple service where the magic actually happens ( I have pre-saved one Pet and one User object, with different name and type)
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
PetRepository petRepository;
public User changeUserAndPet() {
User user = userRepository.findUserById(1L);
Pet pet = petRepository.findPetById(1L);
user.setName("Kevin");
pet.setType("Cow");
userRepository.save(user);
return user;
}
}
Right after calling userRepository.save(user); the Pet object is also updated in the database with new type of 'Cow'. Why exactly does this happen if I only saved the User object? Is this intended to be like this?
There's also one simple controller and simple test endpoint to call the service method which most likely is not important to the question, but I'll still add it here for the sake of completeness.
#RestController
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value = "/test", method = RequestMethod.GET)
public User changeUserAndPet() {
return userService.changeUserAndPet();
}
}
Any explanation / tips are appreciated and feel free to ask extra information / code in github.
The Spring Data repository is a wrapper around the JPA EntityManager. When an entity is loaded, you get the instance, but a copy of the object is stored inside the EntityManager. When your transaction commits, the EntityManager iterates all managed entities, and compares them to the version it returned to your code. If you have made any changes to your version, JPA calculates which updates should be performed in the database to reflect your changes.
Unless you know JPA quite well, it can be tricky to predict when calls are propagated to the database, since flush() is called internally. For instance every time you do a query JPA performs a pre-query flush, because any pending inserts must be send to the database, or the query would not find them.
If you defined a transaction using #Transactional on you method, then pet would be updated even if the user was not saved. When you don't have a transaction, the call to save must trigger the EntityManager to propagate your update to the database. It's a bit of a mystery to me why this happens. I Know that Spring creates the EntityManager inside OpenEntityManagerInViewInterceptor before the Controller is called, but since the transaction is not explicit, it must be created implicitly and there could potentially be multiple transactions.
I always encourage developers to use explicit transactions in Spring, and qualify them with readonly when appropriate.
That's how JPA and the EntityManager works. If you lookup an entity through the repository, it is attached to the EntityManager as managed entity. Any changes that you do to that object, are picked up when a flush is executed by the EntityManager. In fact, you wouldn't even need to call the save method on the repository in your case.
You can find more information about the lifecycle of JPA entities e.g. here: https://dzone.com/articles/jpa-entity-lifecycle
I have a controller method which retrieves an User, then I've got mapped their UserConfig, and then with that UserConfig I retrieve the MainBrands (lazy collection of UserConfiguration).
Let me clarify this:
User Entity:
#Entity
#Table(name = "app_user")
public class User extends BaseEntity {
private UserConfig userConfig;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserConfig getUserConfig() {
return userConfig;
}
//more props..
}
UserConfig Entity:
#Entity
#Table(name = "user_config")
public class UserConfig extends BaseEntity {
private Set<MainBrand> mainBrands;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(...)
public Set<MainBrand> getMainBrands() {
return mainBrands;
}
//more props..
}
And my UserService:
public interface UserService {
public User getById(Long id);
}
So my question is about "best practices" of transactional annotations. I have read more than once, that put #Transactional at Controller level, is bad practice. But in this case I wanna do at Controller:
#RequestMapping(method = RequestMethod.GET, value = "/")
public ModelAndView getMainPage(Long userId) {
ModelAndView = new ModelAndView("/home");
//do stuff
User user = userService.getById(userId);
//some stuff with user
modelAndView.addObject("username", user.getUsername());
//...
List<String> brandsNames = new ArrayList<>();
for(MainBrand mainBrand : user.getUserConfig().getMainBrands()){
brandsNames.add(mainBrand.getName());
}
}
That will fail if don't put the #Transactional annotation at Controller level, because of LazyInitializationException.
So, that's the choices that I've thinked out:
1) With the user make a call to an "UserConfigService" (it's not created now) like userConfigService.getUserConfigByUserId(userId): that's make me think that if I already have the binding at User class, why I would call it again? And I am just creating a new service only for this method.
2) Put the #Transactional annotation at controller level: which makes another problem for my, but it doesn't care in this post.
3) Call the getUserConfig() & getUserConfig().getMainBrands() at UserService so then the collection get initialized: don't like because whenever I use the getById it will initialize the collection even if I do not need it.
So what it would be a good practice for this case? On internet there are always perfect and beautiful examples, but when we start to give some business logic to the project, it turns hard to have a clean code.
Thanks, and sorry for my english.
LazyInitializationException is not related to transactional , it is related to relationship between objects, if your object has a lazy relation,you must fetch your MainBrands objects in your userService.getById(userId) query method before you return your user.
Transactional annotation must be in service class, you can create as many service classes as you need.
I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}
I see a lot of examples where entity objects are returned or passed in session bean methods. But the concept of remote interfaces for session beans make me think that this is bad practice as the entity might hold private data. A client doing remote calls to a session bean shouldn't be able to access all data. I'm just confused? If not, how should I encounter this issue? Should I create classes shadowing the entities public properties?
Let's say I have an entity class for users as following:
#Entity
public class User implements Serializable {
private Integer id;
private String username;
private String email;
private String password;
...
}
For visitors using a java client accessing this entity through a session bean, I want only the id and the username be visible for them. The authenticated user should be able to access all properties expect the password property. Isn't unsafe to have a session bean like this:
#Stateless
public class UserManager {
public User getUserById(Integer id) {
...
}
public List<User> findUsers(String searchKey) {
...
}
}
How should one encounter such security problems?