Hibernate does not allow to save data with similar id - java

I'm learning Spring and few days ago i started learning Hibernate. I have my studying project where i need to create a shop with products. Here is entity class
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "cart")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "quantity")
private int quantity;
#Column(name = "mask")
private String mask;
#Column(name = "price")
private int price;
So, i create interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface CartRepository extends JpaRepository<Cart, Integer> {
}
and create controllers
#Autowired
CartRepository cartList;
#RequestMapping("/add-black-mask")
public String addBlackMask() {
cartList.save(new Cart(1, 1, "black", 3));
return "masks/add-black-mask";
}
#RequestMapping("/add-build-mask")
public String addBuildMask() {
cartList.save(new Cart(2, 1, "build", 5));
return "masks/add-build-mask";
}
#RequestMapping(value = "/save_product_to_cart")
public ModelAndView saveProduct(#ModelAttribute(value = "cart")
Cart cart, BindingResult result) {
int index = Integer.parseInt(String.valueOf(cartList.count()));
if (cart.getId() == 0) {
cart.setId(index + 1);
cartList.save(cart);
} else {
Cart cart1 = cartList.getOne(cart.getId());
cart1.setMask(cart.getMask());
cart1.setQuantity(cart.getQuantity());
cart1.setPrice(cart.getPrice());
cartList.save(cart1);
}
return new ModelAndView("redirect:/");
}
Also, there are some other controllers for view, Thymeleaf etc, but its ok. My problem is - when i save my product 1 time - its ok, but when i save second - it didnt work( i think because i can't save 2 rows with similar ID) So it seems i have UNIQUE ID in my table and it can not be repeated. Question is - how can i delete unique id or change my code in any way? Thanks in advance!
p.s. i read some other topics here but it didnt help me.

when you use GENERATIONTYPE.IDENTITY you are asking hibernate to let the database handle Ids for you, you should not set it yourself. you are changing the value of the id, just create a new product, set all the fields and inside a transactional context, save your product. Also always use wrapped versions of primitives for serialization purposes. (Long is an object but long is a primitive.) you can google boxing and unboxing and learn more about this.

Let me answer this question:
First of all, using annotations as our configure method is just a convenient method instead of coping the endless XML configuration file.
The #Idannotation is inherited from javax.persistence.Id, indicating the member field below is the primary key of current entity. Hence your Hibernate and spring framework as well as you can do some reflect works based on this annotation. for details please check javadoc for Id
The #GeneratedValue annotation is to configure the way of increment of the specified column(field). For example when using Mysql, you may specify auto_increment in the definition of table to make it self-incremental, and then use
#GeneratedValue(strategy = GenerationType.IDENTITY)
in the Java code to denote that you also acknowledged to use this database server side strategy. Also, you may change the value in this annotation to fit different requirements.
1. Define Sequence in database
For instance, Oracle has to use sequence as increment method, say we create a sequence in Oracle:
create sequence oracle_seq;
2. Refer the database sequence
Now that we have the sequence in database, but we need to establish the relation between Java and DB, by using #SequenceGenerator:
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
sequenceName is the real name of a sequence in Oracle, name is what you want to call it in Java. You need to specify sequenceName if it is different from name, otherwise just use name. I usually ignore sequenceName to save my time.
3. Use sequence in Java
Finally, it is time to make use this sequence in Java. Just add #GeneratedValue:
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
The generator field refers to which sequence generator you want to use. Notice it is not the real sequence name in DB, but the name you specified in name field of SequenceGenerator.
4. Complete
So the complete version should be like this:
public class Cart
{
#Id
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
private Integer id;
}
Now start using these annotations to make your JavaWeb development easier.
On top of that I would like you to understand all 4 ways of ID generation in hibernate. You can think of reading in your free time
GenerationType.AUTO
GenerationType.IDENTITY (your case)
GenerationType.SEQUENCE
GenerationType.TABLE {Rarely used nowdays}

Related

Why does hibernate need to save the parent when saving the child and cause a OptimisticLockException even if there no change to the parent?

We are trying to save many child in a short amount of time and hibernate keep giving OptimisticLockException.
Here a simple exemple of that case:
University
id
name
audit_version
Student
id
name
university_id
audit_version
Where university_id can be null.
The java object look like:
#Entity
#Table(name = "university")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class University {
#Id
#SequenceGenerator(name = "university_id_sequence_generator", sequenceName = "university_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "university_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#OneToMany(mappedBy = "student")
#ToString.Exclude
private List<Student> student;
}
#Entity
#Table(name = "student")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class Student {
#Id
#SequenceGenerator(name = "student_id_sequence_generator", sequenceName = "student_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "student_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "university_id")
#ToString.Exclude
private University university;
}
It seem when we assign university and then save Student, if we do more than 4 in a short amount of time we will get the OptimisticLockException.
It seem hibernate is creating update version on the University table even though the University didn't change at the db level.
UPDATE: code that save the student
Optional<University> universityInDB = universidyRepository.findById(universtityId);
universityInDB.ifPresent(university -> student.setUniversity(university);
Optional<Student> optionalExistingStudent = studentRepository.findById(student);
if (optionalExistingStudent.isPresent()) {
Student existingStudent = optionalExistingStudent.get();
if (!student.equals(existingStudent)) {
copyContentProperties(student, existingStudent);
studentToReturn = studentRepository.save(existingStudent);
} else {
studentToReturn = existingStudent;
}
} else {
studentToReturn = studentRepository.save(student);
}
private static final String[] IGNORE_PROPERTIES = {"id", "createdOn", "updatedOn", "auditVersion"};
public void copyContentProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, Arrays.asList(IGNORE_PROPERTIES)));
}
We tried the following
#OptimisticLock(excluded = true)
Doesn't work, still give the optimistic lock exception.
#JoinColumn(name = "university_id", updatable=false)
only work on a update since we don't save on the update
#JoinColumn(name = "university_id", insertable=false)
work but don't save the relation and university_id is always null
Change the Cascade behaviour.
The only one value that seem to made sense was Cascade.DETACH, but give a org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing.
Other solution we though of but are not sure what to pick
Give the client a 409 (Conflict) error
After the 409 the client must retry his post.
for a object sent via the queue the queue will retry that entry
later.
We don't want our client to manage this error
Retry after a OptimisticLockException
It's not clean since when the entry come from the queue we already doing it but might be the best solution so far.
Make the parent owner of the relationship
This might be fine if there are not a big number of relation, but we have case that might go in the 100 even in the 1000, which
will
make the object to big to be sent on a queue or via a Rest call.
Pessimistic Lock
Our whole db is currently in optimisticLocking
and we managed to prevent these case of optimisticLocking so far, we
don't want to change our whole locking strategy just because of this
case. Maybe force pessimistic locking for that subset of the model
but I haven't look if it can be done.
It does NOT need it unless you need it.
Do this:
University universityProxy = universidyRepository.getOne(universityId);
student.setUniversity(universityProxy);
In order to assign a University you don't have to load a University entity into the context. Because technically, you just need to save a student record with a proper foreign key (university_id). So when you have a university_id, you can create a Hibernate proxy using the repository method getOne().
Explanation
Hibernate is pretty complex under the hood. **When you load an entity to the context, it creates a snapshot copy of its fields and keeps track if you change any of it**. It does much more... So I guess this solution is the simplest one and it should help (unless you change the `university` object somewhere else in the scope of the same session). It's hard to say when other parts are hidden.
Potential issues
wrong #OneToMany mapping
#OneToMany(mappedBy = "student") // should be (mappedBy = "university")
#ToString.Exclude
private List<Student> student;
the collection should be initialized. Hibernate uses it's own impls of collections, and you should not set fields manually. Only call methods like add() or remove(), or clear()
private List<Student> student; // should be ... = new ArrayList<>();
*overall some places are not clear, like studentRepository.findById(student);. So if you want to have a correct answer it's better to be clear in your question.
If you enable your query logs from Hibernate, it would be worthwhile to see the queries that your ORM is performing. You'll likely realize that your ORM is doing too much.
In your application properties or config file enable hibernate.show_sql=true
I wouldn't be surprised if your single update to a Student becomes an update to a University which becomes an update to all of its containing Students. Everything gets a version bump.
ORM and entity mappings are for strategically retrieving data. They should not be used to actually define object relationships.
You'll want to visit strategies and design your entities based on how they are used in their REST endpoints.
You specified in your question that you are trying to save a Student but you're noticing that the University also gets updated along with every Student update.
Likely there would never be a time when a Student should ever update a University
Keep your entities lean!
You can structure your entity in such a way that supports this unidirectional relationship. I removed some of the annotation just to demonstrate the structure. You will want to keep in mind that when creating entities, you are writing them for how they are retrieved...
public class University {
#Id
private Long id;
private String name;
private Long auditVersion;
#OneToMany
private List<Student> student;
}
public class Student {
#Id
private Long id;
private String name;
private Long auditVersion;
private Long universityId;
}
This will ensure that updates to the student remains targeted and clean. You are simply assigning a university id to the student therefore establishing that relationship.
You typically want to respect LockExceptions. Retrying upon a LockException is simply bullying your database into submission and will cause more headaches as your application scales.
You always have the option to work with lean entities and create custom response or message objects that would zip the results together.
ORMs are not to be used to create shortcuts
The performance consequence of a SELECT on an indexed/foreign key is roughly the same cost of grabbing them joined... you only introduce a little extra network latency. A second trip to the database is not always a bad idea. (Often times, this is exactly how Hibernate fetches your entities)
You won't have to write queries, but you will still need to understand the retrieval and update strategies.
You're sacrificing database performance and introducing complexity for a convenient .getChild() method. You'll find that you resolve more performance/locking issues by removing annotations, not adding them.

Crnk JsonApiRelation, OneToMany and filtering implementation

I use crnk (JSON-API) in java project and I have 3 questions regarding its usage with spring boot and jpa - haven't found exact implementation details in documentation.
For example, I have 2 entities and respective tables:
#Entity
#JsonApiResource(type = "employee")
public class Employee {
#Id
#JsonApiId
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "typeId")
private EmployeeType employeeType; //stored in table as typeId
}
#Entity
#JsonApiResource(type = "type")
public class EmployeeType {
#Id
#JsonApiId
private int id;
private String typeName;
private int salary;
}
How should JsonApiRelation be introduced in order to be able to call "/employee/1" and "/employee/1/type" urls?
For example there is one more entity.
#Entity
#JsonApiResource(type = "project")
public class Project {
#Id
#JsonApiId
private int id;
private String supervisorName;
private String projectName;
}
First, I'd like to have List of Projects for each Employee, where he is a supervisor, joint by name and have it listed as attribute in Json.
Tried implementing it with #OneToMany and #JoinColumn annotations but got StackOverflowException. How could this be implemented. And second, how could this be implemented with Relation? Like "/employee/1/projects" url.
How should I implement custom filtering of results for findAll method? For example, I have a List of all Employees, but I'd like to exclude some of them from the response. Which class/method should be introduced for this behaviour?
#JsonApiRelation annotation should not be necessary. Crnk will detect the #ManyToOne annotation and map it accordingly.
in case of crnk-jpa it is sufficient to specify all relationships in JPA. Matching JSON API relationships. So your approach seems good. What was the StackoverflowException stacktrace? (next to the examples, there are also many example entities in crnk-jpa)
I would make use of a decorator. See http://www.crnk.io/documentation/#_request_filtering. RepositoryDecoratorFactory allows to place a custom repository between the caller and crnk-jpa (or any other kind of repository). There you can do any kind of modification perform (maybe) calling the "real" repository. => Will add an example for this
feel free also make open up tickets in crnk for any documentation/example clarifications.

Import data using JPA keeping same identifiers

I've developed a simple web app which use some database tables using JPA. Let's say Table A and Table B with reference to Table A. My app runs in different environments on my customers like development, testing and production stage. Because of that I have to create a process to export/import data between these environments.
When I move the objects I want to keep the same ID because I want to keep the same references. Is there any way to do that with JPA?
I am using Hibernate 4.3.8 with Oracle Database. This is my Entity:
#Entity
#Table
public class Category
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
And I am trying to do that:
Category category = new Category();
category.setId(1L);
category.setName("Test");
EntityManager em = PersistenceManager.getEntityManager();
em.merge(category);
But it uses the hibernate sequence to generate the ID instead of using the given one. If I use persist instead of merge it throws an exception because it is a detached object.
Is there any way to do that ?
Yes, you can define a custom id generator using a defined sequence
You need a class which extends org.hibernate.id.SequenceGenerator:
package yourPackage;
public class CustomGenerator extends SequenceGenerator
{
#Override
public Serializable generate(SessionImplementor session, Object obj)
{
...
}
}
And to use your custom generator in your entity:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CustomGenerator")
#GenericGenerator(name = "CustomGenerator",
strategy = "yourpackage.CustomGenerator",
parameters = {
#Parameter(name = "sequence", value = "custom_id_sequence")
})
private Long yourId;

Why do I get shared Ids with JPA?

I've a question about JPA and Inheritance (EclipseLink) :
For a school project, I've created an abstract class : HumanEntity
and different subclasses which implement it.
The abstract class:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class HumanEntity implements IHuman{
#Id
#GeneratedValue
protected long id;
#Column(name = "FIRST_NAME")
protected String firstName;
#Column(name = "LAST_NAME")
protected String lastName;
protected LocalDate birthDate;
protected HumanEntity(){
}
#Override
public String getFirstName() {
return firstName;
}
#Override
public String getLastName() {
return lastName;
}
#Override
public LocalDate getBirthDate() {
return birthDate;
}
}
A subclass example :
#Entity
#Table(name="ACTOR")
public class ActorEntity extends HumanEntity{
protected ActorEntity(){}
protected ActorEntity(ActorBuilder actorBuilder){
this.firstName = actorBuilder.getFirstName();
this.lastName = actorBuilder.getLastName();
this.birthDate = actorBuilder.getBirthDate();
}
}
When I run the the project :
-> Ids are shared for every subclasses then I have
In the Actor Table :
1 -> ....
3 -> ....
In the director table :
2 -> ....
How can I create a different Id for every subclasses but with a protected (shared) Id in my code ?
Another little question EclipseLink ,or JPA always create a sequence table but i dont know what is it used for ? can I delete it ?
Thanks a lot !
All objects that are of type HumanEntity or subclasses are instances of HumanEntity, hence the id's need to be consistent, and hence from the same "group" of ids.
You CANNOT have say id 1 for an Actor and id 1 for a Director since they are both HumanEntity, and you need to uniquely identify a HumanEntity. i.e Actor#1 is also HumanEntity#1. So you cannot also have Director#1 since that would also be HumanEntity#1!
If you want to have id's like those then you would need to change your inheritance structure.
First of all it would be interesting which database platform you are using. As you mentioned a school project I assume it is either PostgreSQL or MySQL?!
The ID's values are based on the strategy (see GenerationType) that is used along with the #GeneratedValue annotation. If you don't specify any specific strategy for a #GeneratedValue-field, the default strategy should be javax.persistence.GenerationType.AUTO according to the JPA JavaDoc of GeneratedValue. However EclipseLink seems to use javax.persistence.GenerationType.TABLE according to their documentation:
GenerationType:
The strategy of the GeneratedValue is defined by the GenerationType enumerated type. The strategy defines how the id value should be generated on the database. By default, EclipseLink chooses the TABLE strategy using a table named SEQUENCE, with SEQ_NAME and SEQ_COUNT columns, with allocationSize of 50 and pkColumnValue of SEQ_GEN. If SEQUENCE is used the default database sequence used is named SEQ_GEN_SEQUENCE with an allocationSize of 50.
This basically means that EclipseLink will use a non business database table SEQUENCE to lookup the next values. Furthermore it will use the SEQ_GEN row of this table for generation of all default generated values. That is all entities will share a common pool of generated values by default. I don't have a DMBS at hand right now but looking into this table will probably give you more insights on the used values.
If you want to use a different strategy or another table for generation you should take a look at the JavaDoc of TableGenerator and SequenceGenerator. That way you could create a separate sequence for each subclass.
I think the above already answered your question regarding the sequence table (at least I hope so). So I'd rather not delete it...

JPA primary key value is always 0

I have a post class and it kind of works, but there's one problem: the primary key doesn't increase.
#Entity
#Table(name="posts")
public class Post extends GenericModel{
#Id
#Column(name="post_id")
public int id;
#Column(name="post_situation")
public String situation;
#Column(name="post_date")
public Date date;
#Column(name="post_userid")
public int userid;
#OneToMany(mappedBy="post", cascade=CascadeType.ALL)
public List<Block> blocks;
public Post addBlock(String content, int position){
Block b = new Block(this, content, position);
b.save();
this.blocks.add(b);
this.save();
return this;
}
public Post(String situation, Date date){
this.situation = situation;
this.date = date;
this.userid = 2;
}
}
When I call it the first time on an empty table, it works fine, but the second time, I'm getting PersistenceException occured : org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
The post_id column always has 0. Any idea how to fix this? I have the #Id annotation in palce..
This is how I have in my controller:
Post p = new Post("Midden in het middenoosten.", new Date()).save();
Any ideas what's causing this problem?
It seems that you want the primary key values to be auto-generated. If that is the case, if you'll need to add the #GeneratedValue annotation to the id attribute, in addition to the #Id annotation. Your code should therefore be:
#Id
#Column(name="post_id")
#GeneratedValue
public int id;
There are several strategies available to generate the Ids. You would have to read up on those to decide if you want to choose the TABLE -based, SEQUENCE -based or the IDENTITY -based strategy (which depends on what your database supports). If you choose a strategy explicitly, the define strategy will be used, instead of the default AUTO strategy. Explicit strategy decisions, are communicated in code as:
#Id
#Column(name="post_id")
#GeneratedValue(strategy=SEQUENCE, generator="POST_SEQ")
public int id;
Without generated values, the default value for integers in Java, i.e. 0 will be persisted for the post_id column. Due to the primary key constraint, you cannot have a second row with the same key, resulting in the described failure.
There are several strategies available to generate id:
GenerationType.AUTO
GenerationType.SEQUENCE
GenerationType.IDENTITY
GenerationType.TABLE
If you want the primary key values to be auto-generated, use GenerationType.AUTO, it works with MySQL.

Categories