Spring data-JPA repository.save() is not updating entity - spring

My classes are like..
Employee Entity:
import javax.persistence.Entity;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.PrePersist;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
#Entity
#Data
#DynamicUpdate
#EntityListeners(AuditingEntityListener.class)
#Table(name = "tbl_employee", indexes = {
#Index(name = "idx_employee_status", columnList = "status"),
#Index(name = "idx_employee_createdAt", columnList = "createdAt") })
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
private String name;
private String remarks;
private String status;
#PrePersist
public void setCreatedAt() {
this.createdAt = OffsetDateTime.now();
}
}
Repository:
public interface EmployeeRepository extends CrudRepository<Employee, Long>, PagingAndSortingRepository<Employee, Long>{
}
Service methods:
createEmployee(){
Employee employee = new Employee();
employee.setName("John");
employee.setRemarks("Very Good Performance");
employeeRepository.save(employee);
}
updateEmployee(){
Employee employee = employeeRepository.findById(1L); // Id is long
employee.setName("Thomas");
employee.setRemarks("Above average Performance");
employeeRepository.save(employee);
}
createEmployee() is working and data is getting saved in DB (MySQL), but updateEmployee() is not. No sql query is generated in the eclipse console.
AM I missing something in configuration?

Make sure that the service methods are annotated with #Transactional.
Otherwise the hibernate cannot track changes which are needed for #DynamicUpdate.
Alternatively you can also try to remove the annotation.

Hibernate/JPA only access the database when it has to.
In the create variant it needs to return an instance with a updated ID value. The id value gets generated by the database when the insert is performed.
Therefore the insert is performed immediately.
In the other case no information from the database is required.
Therefore Hibernate/JPA only marks the entity as dirty and moves on.
Only when the transaction ends the update is actually performed.

Related

Spring Boot JPA returns correct count but no data

Evening,
I have a Spring application that is connected to a PostgresSQL db. I can connect to the database and see that the query is returning the correct number of elements for the array but nothing in them:
curl http://localhost:8080/books
[{},{},{}]%
My Book model looks like this:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
private String author;
private BigDecimal price;
public Book() {}
public Book(String name, String author, BigDecimal price) {
this.name = name;
this.author = author;
this.price = price;
}
}
and the controller:
#RestController
public class BookController {
#Autowired
private BookRepository repository;
// Find
#GetMapping("/books")
List<Book> findAll() {
List<Book> books = repository.findAll();
System.out.println(books);
return repository.findAll();
}
}
I've looked at these questions here, here and here but those answers didn't fit with this.
What am I not doing to see data come back?
In order for your entity to be serialized by Spring the entity needs to have getters for its properties. You could use lombok to auto-generate getter/setters for you entity properties or just write them your own.

JPA: Data integrity violation instead of Upsert

I have this entity with these fields and this primary key:
#Getter
#Setter
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "MY_TABLE", schema = "MY_SCHEME")
public class MyEntity{
#Id
#Column(name = "ID")
private String id;
#Column(name = "NAME")
private String name;
#Column(name = "DESCRIPTION")
private String description;
}
I'm experiencing some undesirable behavior.If I try to insert the exact same data, I was expecting a primary key violation because it already exists, but I'm finding that what it's doing is an upsert. I am extending my repository from JpaRepository and using the save method:
#Repository
public interface MyJpaRepository extends JpaRepository<MyEntity, String> {
}
In my service:
...
this.repository.save(myEntity);
...
The database is Oracle, and if I launch the insert manually in SQL developer, the data violation occurs.
That could be happening?
Based on source code of save:
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) { //it checks if entity is new based on id. i.e. insert operation.
em.persist(entity);
return entityx
} else {
return em.merge(entity); // just merging i.e. in your case doing upsert.
}
}
So currently save method works as expected; but if you really want to avoid upsert behaviour you might want to try using existsById; something like below:
if(!repository.existsById(..)){
repository.save(..);
}

CascadeType Merge is ignored when Persist is set

Hy all
I'm having a hard time solving the following spring jpa problem.
Let's say I have the following simple data model (two entities with a one direction relationship between the two)
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity1 extends AbstractEntity {
private String name;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity2 extends AbstractEntity {
private String name;
#ManyToOne(cascade={ALL})
private Entity1 entity1;
}
and the following plumbing to store them
public interface Entity1Dao extends JpaRepository< Entity1, Long >, JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2, Long >, JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
#Service
public class StoreService {
#Autowired
Entity1Dao dao1;
#Autowired
Entity2Dao dao2;
#Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
#Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
#SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
And the following test
#SpringBootTest
#TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
#Autowired
StoreService store;
#Test
#Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
#Test
#Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
#Test
#Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1", new Entity1("new entity1")));
}
#Test
#Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
}
the last test (i.e. insertEntity2WithExistingEntity1) fails with the following exception
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.example.jpaDemo.Entity1
If I change the CascadeType in Entity2 to MERGE, that test passes but the insertEntity2WithNewEntity1 fails with the following exception
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing : com.example.jpaDemo.Entity2.entity1 ->
com.example.jpaDemo.Entity1
I've tried multiple combination of cascading types bute it seems that as soon as PERSIST is used, the last test fails (and ALL includes PERSIST).
I would have expected that if MERGE and PERSIST are set, they would both be active but form the test it looks like MERGE is ignored when PERSIST is set.
Any clues, tips, hints at what I'm doing wrong so that both tests run???
EDIT
The tests are suppose to mimick the behaviour of a REST service endpoint reveiving and saving json reprensentation of an Entity1.
The json for the third test would be
{ name: "with new entity1", entity1: { name: "new entity1"}}
The json for the fourth would be
{ name: "with new entity1", entity1: { id: 1, version: 0, name: "test entity1"}}
JPA should persists the entity1 in the third test because it's id is null but should merge the one in the fourth test because it's id is not null.
I am however unable to do both, it's either one or the other.
EDIT 2
I've modified Entity1 slightly to have a reference to the list of Entity2 associated to it and annotated it with #OneToMany and the same cascading type as in Entity2 and it's the same behavior.
When I set the cascading type to MERGE and only Merge, I'm able to save a new entity that has a reference with an existing one but I can't save a new entity with a reference to a new one.
When I set the cascading type to PERSIST (i.e PERSIST on its own, PERSIST and MERGE or ALL), it's the oppposit; I can save a new entity with a reference to anther new entity but I can't save a new entity with a reference to an already existing one.
So it's seem that when PERSIST is set, it overrides the behavior of MERGE. That, to me, is a bug. Is it not?
I've uploaded the source to github in case you want to experiment or take a look at it yourself. https://github.com/willix71/persistVsMerge.git
You need to add #Transactional on your last test. The entity loaded is detached as there is no outer transaction, you can't persist it.
#Test
#Order(5)
#Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
I'm not sure if this is relevant anymore, but the code below works as I would expect. Removing "cascade = CascadeType.PERSIST" will fail the persist test with "object references an unsaved transient instance".
I also noticed in your github repo that you are attempting to do cascading both from parent to child and child to parent. I think this is the root cause of your issues.
Entities:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
#ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#OneToMany(mappedBy = "address")
List<User> user;
}
Repositories:
public interface UserRepository extends JpaRepository<User, UUID> {
}
public interface AddressRepository extends JpaRepository<Address, UUID> {
}
Tests:
#DataJpaTest
#Import(DataSourceConfig.class)
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Autowired
private AddressRepository addressRepository;
#Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
#Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}

OneToMany bidirectional relationship JoinColumn value is null in Spring Data JPA

I have OneToMany bidirectional mapping for two entities Cart and CartProduct. Whenever we insert a Cart object with cart products, CartProduct table should fill with cart_id. Here is the problem, when I insert cart object, everything seems to be fine except, JoinColumn(card_id) which results in a null value in CartProduct table. Am I doing this right?
Cart.Java
package com.springtesting.model.cart;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.springtesting.model.AbstractAuditingEntity;
import com.springtesting.model.user.UserProfile;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
#EqualsAndHashCode(callSuper = true)
#Entity
#Data
#Table(name = "cart")
public class Cart extends AbstractAuditingEntity
{
private static final long serialVersionUID = 6294902210705780249L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne
#JoinColumn(name = "user_profile_id")
#JsonIgnoreProperties(value = {"addresses"})
private UserProfile userProfile;
#ManyToOne
#JoinColumn(name = "cart_status")
private CartStatus cartStatus;
#OneToMany(mappedBy = "cart", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
//#ElementCollection(targetClass = CartProduct.class)
private List<CartProduct> cartProducts=new ArrayList<>();
}
CartProduct.Java
package com.springtesting.model.cart;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.springtesting.model.AbstractAuditingEntity;
import com.springtesting.model.product.Product;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
#EqualsAndHashCode(callSuper = true)
#Entity
#Data
#Table(name = "cart_product")
public class CartProduct extends AbstractAuditingEntity
{
private static final long serialVersionUID = 6498067041321289048L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne
#JoinColumn(name = "product_id")
private Product product;
#Column(name = "quantity")
private Integer quantity;
#ManyToOne
#JoinColumn(name = "cart_id",referencedColumnName = "id")
#JsonIgnoreProperties(value = {"userProfile","cartStatus","cartProducts"})
private Cart cart;
}
TestCase.java
#Test
public void insertCart()
{
Cart cart=new Cart();
cart.setUserProfile(userProfileRepository.findAllByUserId(1L).get());
cart.setCartStatus(cartStatusRepository.findById(1L).get());
List<CartProduct> cartProducts=new ArrayList<>();
CartProduct cartProduct=new CartProduct();
cartProduct.setProduct(productRepository.findById(1L).get());
cartProduct.setQuantity(2);
cartProducts.add(cartProduct);
cartProduct=new CartProduct();
cartProduct.setProduct(productRepository.findById(2L).get());
cartProduct.setQuantity(1);
cartProducts.add(cartProduct);
cart.setCartProducts(cartProducts);
cartRepository.saveAndFlush(cart);
}
Yes, your fix is the addition of cartProduct.setCart(cart); This is because the CartProduct is the owning entity and is the keeper of the foreignKey. The above statement sets the FK.
The way to think about this is the concept of owning entity. When you have mappedBy="cart" you are saying that the CartProduct class owns the relationship. This means that only the CartProduct class is doing the persisting. This tells JPA to create a FK in the CartProduct table. However, we notice that save is not being called on CartProduct but rather on Cart and yet cartProducts is still being saved. This is because you have the cascade = CascadeType.ALL annotation. This tells JPA to cascade certain operations when they are done to Cart, in this case the save operation.
You should have SQL statements printed and examine the differences with different configurations and test cases. This will help you understand better.
You also have FetchType.EAGER. This is generally a bad habit and usually leads to endless problems.
A good way to think about a bidirectional mapping is that the List<CartProducts> cartProducts is a query only field. In order to save a CartProduct you would call save on the cartProductRepository directly. E.g.
CartProduct cartProduct=new CartProduct();
cartProduct.setProduct(productRepository.findById(1L).get());
cartProduct.setQuantity(2);
cartProduct.setCart(cart);
cartProductRepository.save(cartProduct);
and then
cart.getCartProducts().add(cartProduct);
and remove all the cascade and eager fetch annotations. When hibernate says that you must management both sides of the relationship this is what is meant.
Doing it this way will result in one query for the save. By using a cascade annotation you will find that as you add items to the cart and call save on it the sql generated will first delete all the existing cartProducts items from the database and re-add them along with the new one every time you call save. For a cart with 10 items instead of a single save you will have a delete and 10 new saves. Definitely less desirable. If you have to reload the cart from scratch the most efficient method is to get the cart and then cart.setCartProducts(cartProductRepository.findAllByCart(cart)); which is what FetchType.EAGER is doing anyway. When you understand all this then you realize that you don't need a = new ArrayList<>(); for your cartProducts.
I think I figured out the solution. Based Hibernate docs
Whenever a bidirectional association is formed, the application
developer must make sure both sides are in-sync at all times.
So I manually added the cart object to cartProduct object, which saves cart_id in CartProduct table
CartController.java
import com.pj.springsecurity.model.cart.Cart;
import com.pj.springsecurity.model.cart.CartProduct;
import com.pj.springsecurity.repo.CartRepository;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
#RestController
#RequestMapping("/api/v1/cart")
public class CartController
{
private final CartRepository cartRepository;
public CartController(CartRepository cartRepository)
{
this.cartRepository = cartRepository;
}
#GetMapping(path = "/list")
public List<Cart> getAllCarts()
{
return cartRepository.findAll();
}
#GetMapping(path = "/find/user/{id}")
public Optional<Cart> getCartBasedOnUserId(#PathVariable Long id)
{
return cartRepository.findAllByUserProfileUserId(id);
}
#PostMapping(path = "/product/add")
public Cart addProductToCart(#RequestBody Cart cart)
{
List<CartProduct> cartProducts=cart.getCartProducts();
for(CartProduct cartProduct: cartProducts)
{
cartProduct.setCart(cart);
}
return cartRepository.saveAndFlush(cart);
}
#PutMapping(path = "/update")
public Cart updateCart(#RequestBody Cart cart)
{
return cartRepository.saveAndFlush(cart);
}
#DeleteMapping(path = "/delete")
public Cart createEmptyCart(#RequestBody Cart cart)
{
return cartRepository.saveAndFlush(cart);
}
#DeleteMapping(path = "/product/delete")
public void deleteProductFromCart(#RequestBody Cart cart)
{
List<CartProduct> cartProducts=cart.getCartProducts();
for(CartProduct cartProduct: cartProducts)
{
cartProduct.setCart(null);
}
cartRepository.delete(cart);
}
}
and Test case updated with the same
#Test
public void insertCart()
{
Cart cart=new Cart();
cart.setUserProfile(userProfileRepository.findAllByUserId(1L).get());
cart.setCartStatus(cartStatusRepository.findById(1L).get());
List<CartProduct> cartProducts=new ArrayList<>();
CartProduct cartProduct=new CartProduct();
cartProduct.setProduct(productRepository.findById(1L).get());
cartProduct.setQuantity(2);
cartProduct.setCart(cart);
cartProducts.add(cartProduct);
cartProduct=new CartProduct();
cartProduct.setProduct(productRepository.findById(2L).get());
cartProduct.setQuantity(1);
cartProduct.setCart(cart);
cartProducts.add(cartProduct);
cart.setCartProducts(cartProducts);
cartRepository.saveAndFlush(cart);
}

Change default sort order for Spring Data findAll() method

I'm using Spring Data JPA and I wonder if it is possible to change the default sort order for a entity being used by the Spring Data findAll() method?
You can achieve this as follows:
dao.findAll(new Sort(Sort.Direction.DESC, "colName"));
// or
dao.findAll(Sort.by("colName").descending());
Another way to achieve the same. Use the below method name:
findByOrderByIdAsc()
You should be able to do this by either:
in spring-data 1.5+, overriding the findAll() method in your Interface, adding the #Query annotation and creating a named Query in your Entity class like, for example, below:
Entity
#Entity
#NamedQuery(name = "User.findAll", query="select u from User u order by u.address.town")
public class User{
}
Repository
public interface UserRepository extends ... <User, Long> {
#Override
#Query
public Iterable<User> findAll();
}
or,
by creating a custom repository implementation:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
Use a PagingAndSortingRepository instead.
With that in place you can add a queryparameter ?sort=,
Repository:
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
//no custom code needed
}
GET Request:
localhost:8080/users?sort=name,desc
If you want to add costom query to findAll() jpa query you can do it this way
here i changed my default order
According to my default order is primary key it is id
but now i here set id_order to change my default order
Model class
#Entity
#Table(name = "category")
#NamedQuery(name = "Category.findAll", query="select u from Category u order by
u.id_order")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nameEn;
private String nameSi;
private String nameTa;
private Integer id_order;
Repository class
import com.model.Category;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface CategoryRepository extends CrudRepository<Category, Integer> {
#Override
#Query
public Iterable<Category> findAll();

Resources