JPA: Data integrity violation instead of Upsert - spring-boot

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(..);
}

Related

How to correctly describe entities with many-to-many relationship, Spring Boot JPA

I have those entities:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Tender {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, updatable = false)
private Long id;
private String source;
private String sourceRefNumber;
private String link;
private String title;
#Column(columnDefinition="TEXT")
private String description;
private String field;
private String client;
private LocalDate date;
private LocalDate deadline;
#ManyToMany
private List<Cpv> cpv;
}
And CPV:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Cpv {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
}
Each Tender can have list of Cpv-s.
In my DB I have already list of all CPV codes with description, so when I add new Tender to DB, it should add record to tender_cpv table with tender_id and cpv_id.
But when I'm using this method in my TenderServiceImpl to set Cpv id-s from DB I got error after that when try to save Tender:
#Override
public Tender addNewTender(Tender tender) {
if(tender.getCpv() != null) {
for(Cpv cpv : tender.getCpv()) {
cpv = cpvRepository.findCpvByCode(cpv.getCode());
}
}
tenderRepository.save(tender);
return tender;
}
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.supportportal.domain.Cpv;
I understand that somewhere in the description of the entities a mistake was made, because earlier I did not have a database with all the CPV codes and before saving the tender I saved all the CPVs, but now I need to redo the logic to use the existing CPV database.
Please advise how can I change the entity description.
addNewTender method changes solved my problem:
#Override
public Tender addNewTender(Tender tender) {
if(tender.getCpv() != null) {
List<Cpv> dbCpvs = new ArrayList<>();
for(Cpv cpv : tender.getCpv()) {
dbCpvs.add(cpvRepository.findCpvByCode(cpv.getCode()));
}
tender.setCpv(dbCpvs);
}
tenderRepository.save(tender);
return tender;
}
In order for the existing entities from the database to bind to the new object, we had to first get each of them from the database and bind to the new entity.

JPA issue (lazy loading? eager ?)

Iam building a simple Spring Boot app, with 2 entities:
- Student model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String password;
private boolean active;
private Date dob;
private String roles;
#ManyToOne
private Training training;
}
- Training model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Training {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int duration;
#OneToMany(mappedBy = "training")
#JsonIgnore
private Collection<Student> students;
}
EDIT
I run the app by adding 2 resources in the db:
public static void main(String[] args) {
SpringApplication.run(MsSchoolingSbApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Training t1=trainingRepo.save(new Training(null,"php", 20, null));
Training t2=trainingRepo.save(new Training(null,"java", 20, null));
Student st=new Student(null, "XXXX", "ZZZZ", true,new Date(),"ADMIN",t1);
Student st2=new Student(null, "XXXXX2", "ZZZZZ2", true,new Date(),"USER",t2);
studentRepo.save(st);
studentRepo.save(st2);
}
END EDIT
EDIT 2
- StudentRepo
#RepositoryRestController
public interface StudentRepo extends JpaRepository<Student, Long>{
public List<Student> findByNameStartsWith(String name);
Optional<Student> findByName(String name);
}
- TrainingRepo
#RepositoryRestController
public interface TrainingRepo extends JpaRepository<Training, Long> {
}
END EDIT 2
i've tried to put fetch = FetchType.EAGER or LAZY, i've also added #JsonIgnore but as soon as i fill the db with new data (trainings and students) and run the app, i get this message:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.schooling.models.Training.students, could not initialize proxy - no Session
What am i doing wrong ?
The problem you got must have related to how you use those 2 entities so you need to provide more information about how you use it.
You might want to look out for your problem in this tutorial: https://www.baeldung.com/hibernate-initialize-proxy-exception
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes
EDIT
you can exclude the relation from the toString method by adding, for example in my case:
#ToString(exclude = {"students"})

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);
}
}

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

Spring JPA saving distinct entities with composite primary key not working as expected, updates same entity

I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?

Resources