Auto generated id is generated based on previous JpaRepository.save() - java

I have two entity class:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// getter and setter
}
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// getter and setter
}
I have the repositories which extends JpaRepository.
A a = new A();
B b = new B();
aRepo.save(a); // generates id = 1
bRepo.save(b); // generates id = 2
Since I am inserting to different tables, I would like the ids to be generated based on the previous id in that particular table and not depending on the previous insertion in some other table. Is there any way to do this?
I am using MySQL as my db.

I think what you're looking for is GenerationType.IDENTITY.
GenerationType.AUTO vs GenerationType.IDENTITY in hibernate may help you

Related

Overriding id generated by sequence

I am using SequenceGenerator for generating a primary key for my entities. Now there is a use case wherein I need to use the given id instead of the id generated by the sequence.
I tried setting the id field by given value and saving it but the custom id has been overridden by the sequence. So is there any way to tell the hibernate to use the given id if it is not null or generate a new one using sequence if the id is not set explicitly?
My current id generator
#Id
#SequenceGenerator(name = "userSeqGen", sequenceName = "userSeq", initialValue = 100, allocationSize = 50)
#GeneratedValue(generator = "userSeqGen")
private Integer id;
If the #Id field of an entity does not have #GeneratedValue , it allows you to assign the ID for that entity manually.
That means you can create another entity that maps to the same table for the use-case that require to manually configure the ID and use the entity with #GeneratedValue for the normal case.
By using some inheritance design , you may have something like :
#MappedSuperclass
public class Employee {
#Column
private String email;
#Column
private String title;
......
}
#Entity
#Table(name="employee")
public class DefaultEmployee extends Employee {
#Id
#SequenceGenerator(name = "userSeqGen", sequenceName = "userSeq", initialValue = 100, allocationSize = 50)
#GeneratedValue(generator = "userSeqGen")
private Integer id;
}
#Entity
#Table(name="employee")
public class ManuallyAssignedIdEmployee extends Employee {
#Id
private Integer id;
public ManuallyAssignedIdEmployee(Integer id){
this.id = id;
}
}
If you need to create an employee with manually assigned id , create ManuallyAssignedIdEmployee instance. Otherwise , create DefaultEmployee instance.
Please note that since you are manually assigned ID for some cases , it is your responsibility to make sure that the manually assigned ID will not mess up with the ID that is auto-generated from the DB sequence. (e.g the manually assigned Id is already auto-generated and used by other records or the auto-generated ID in the future cannot be used as you already manually assigned it to some records in the past etc.)

How to provide Initial value OR Increment ID with JPA GenerationType.AUTO

I am using following code to define MyEntity,
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
For the first POST after my application starts, I create MyEntity everything works fine, MY_TABLE_ID starts with 1 and works as expected.
My issue is, If somebody inserts data manually before I do my POST then I get duplicate key exception as myTableId is entered as 1 which is already present.
My main problem is I can't create database sequence for using GenerationType.SEQUENCE now to resolve this as database can't be altered now.
I have tried various combinations of GenerationType, TableGenerator but I am unable to successfully tackle it.
Setting initialValue to some larger number to avoid duplicate values can temporarily resolve my problem but I am unable to do it too.
If someone can help me with initialValue with AUTO or give me some other better solution without database changes will be great :)
As MY_TABLE_ID is an identity column, following annotations will work.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY) // <-- IDENTITY instead of AUTO
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
The identity column will automatically assign an value as soon as the transaction is committed. You are not to set any values for an identity column, as its the job of the database to assign the values. Therefore you also don't need to think about any initial values (forget them completely for identity columns)
I tried various options in answers provided here and for similar questions on stackoverflow and other forums,
I had few limitations,
I couldn't create database sequence as my database changes were freezed.
I didn't want to introduce new Custom IdGenerator class because it would add confusion to other people working with me.
It was resolved using following change:
Adding GenericGenerator with increment strategy helped me, I made following changes to my code.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq")
#GenericGenerator(name = "seq", strategy="increment")
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
It helped me because,
From Hiberbate DOCs
increment
An IdentifierGenerator that returns a long, constructed by counting
from the maximum primary key value at startup. Not safe for use in a
cluster!
Since, it was incrementing already existing myTableId even if it was manually inserted, this resolved my issue.
You can also implement your own generator if you need more control.
See this interface IdentifierGenerator.
So you can get the count of records, for example through a #NamedQuery.
Then you can generate an identifier yourself.
public class MyEntityKeyGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) {
// SELECT count(ent) from MyEntity ent;
Long count = (Long) session.getNamedQuery("count-query").uniqueResult();
// calc and return id value
}
}
Entity:
class MyEntity {
#Id
#GenericGenerator(name = "my_generator",
strategy = "org.common.MyEntityKeyGenerator")
#GeneratedValue(generator = "my_generator")
private Long id;...
Just do not forget about the lock.
I use the generation type Identity, which basically means that the db, takes care of Id generation.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#MappedSuperclass
#EntityListeners(EntityListener.class)
#EqualsAndHashCode(of = {"id", "createdAt"})
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED_AT", updatable = false)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
}
You can also use, Sequence generation:
#Entity
#SequenceGenerator(name="seq", initialValue=1, allocationSize=100)
public class EntityWithSequenceId {
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#Id long id;
}

Two ways to identify the same entity for easier testing

I have a class, for example
#Setter
#EqualsAndHashCode(of = "id")
#Entity
public class MovieBoxOfficeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
}
which has an ID generated automatically without being manually set. I also have a class with a method
#Entity
#Table(name = "movies")
public class MovieEntity {
List<MovieBoxOffice> boxOffices = new ArrayList<>();
public void addBoxOffice(MovieBoxOffice boxOffice) throws ResourceConflictException {
if (this.boxOffices.contains(boxOffice)) {
throw new ResourceConflictException("A box office with id " + boxOffice.getId() + " is already added");
}
this.boxOffices.add(boxOffice);
}
}
There is a problem with testing the method addBoxOffice, because the comparison of MovieBoxOfficeEntity objects is done using the ID, and the ID is only generated automatically when writing to the database and can not be set manually.
I came up with the idea of adding the uniqueId field to the MovieBoxOfficeEntity class and adding it to the #EqualsAndHashCode annotation
#Setter
#EqualsAndHashCode(of = {"id", "uniqueId"})
#Entity
public class MovieBoxOfficeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
private String uniqueId = UUID.randomUUID().toString();
}
then I can test this way
#Test(expected = ResourceConflictException.class)
public void canAddBoxOffice() throws ResourceConflictException{
final String id = UUID.randomUUID().toString();
final MovieBoxOfficeEntity boxOffice = new MovieBoxOfficeEntity();
boxOffice.setUniqueId(id);
this.movieEntity.addBoxOffice(boxOffice);
this.movieEntity.addBoxOffice(boxOffice);
}
in this way, the object boxOffice will have
id: null
uniqueId: some generated UUID
and the comparison of objects will take place on the uniqueId comparison.
What do you think about creating a uniqueId field just to test the entity methods?
Adding uniqueId can lead to collisions. Suppose there are two finders. When their result is created, two instances will be created for the same rows in the database. They will have the same id, but different uniqueId. Means, two objects have the same primary key, but equals return false. This can lead to errors in the application logic and in the JPA implementation.
You can override equals. But use the entity attributes that are related to your table, not the randomly generated UUIDs.

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;

Using JPA mapped by not primary key field?

I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.
The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.

Categories