Overriding id generated by sequence - java

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.)

Related

idMoneda property not updated in DB with #Transient directive

I have this entity, in which I have made a PUT and POST method, which do not give an error but nevertheless the idMoneda, which is a property calculated with #Transient because it is the ID of the moneda(where there is a 1 to 1 relationship with another table), it does not update me, when I look at the database it remains null even though in the POST request I put a value. I don't know if it's because the setter is wrong, or just that something else needs to be added that I don't see right now.
#Entity
#Table(name = "REMESA")
public class Remesa {
#Id
#SequenceGenerator(name = "remesa_sequence", sequenceName = "remesa_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "remesa_sequence")
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "moneda", nullable = true)
#JsonIgnore
private Moneda moneda;
#Transient
#JsonProperty("moneda")
private Long idMoneda;
public Long getIdMoneda() {
return this.moneda.getId();
}
public void setIdMoneda(Long idMoneda) {
this.idMoneda = idMoneda;
}
}
#Transient in JPA means: do not save this field in DB. A column named "moneda_id" will automatically be generated by your relationship if it's well-defined
Java's transient keyword is used to denote that a field is not to be serialized, whereas JPA's #Transient annotation is used to indicate that a field is not to be persisted in the database, i.e. their semantics are different. Because they have different meanings.
So try to remove the transient annotation and run your code .

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

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

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.

How to use generated sequence number before persisting?

My scenario is very simple. I have an entityID identity field in the #Entity class and the DB (Oracle, which I'm not sure that matters):
#Id
#SequenceGenerator(name="ENTITY_SEQ_GEN", sequenceName="SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ENTITY_SEQ_GEN")
#Column(name="ENTITY_ID")
private long entityID;
I have another field called, let's say, entityReadableID and that should be a String consisting of the stringified entityID concatenated with another String field from the entity. E.g. if entityID is 1234, entityReadableID may be something like 1234ABC.
My problem is that, as far as I know, the value of entityID is not known before the row is created in the DB but I need to concatenate the entityReadableID using its value. Is there a way to fetch the value of the sequence generated ID before the row is created in the DB so that I can use it to generate the other ID? I know I can make it an insert with that field being null and then make an update once I know what entityID is but that solution seems less than elegant.
The way I am hoping Hibernate/Oracle may be able to support this is if Hibernate can somehow "reserve/issue" the next generated value for the entity being processed before the actual persistence, let me know what it is so I can manipulate with it, then at the end persist it.
You can get the generated Id before persisting of that entity by not using sequence directly for that entity, I mean use a separated entity for that sequence, so your entity should be something like:
#Entity
#Table(name = "ENTITY"
)
public class EntityClass implements java.io.Serializable {
private Long entityId;
private String entityRelatedId;
public EntityClass() {
}
// you may have other constructors
#Id
#Column(name = "ENTITY_ID", nullable = false)
public Long getEntityId() {
return this.entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
#Column(name = "ENTITY_RELATED_ID", length = 50)
public String getEntityRelatedId() {
return this.entityRelatedId;
}
public void setEntityRelatedId(String entityRelatedId) {
this.entityRelatedId = entityRelatedId;
}
}
and the entity for the sequence is something like:
#Entity
#Table(name = "SEQ_GENERIC_TBL"
)
public class SeqGenericTbl implements java.io.Serializable {
private Long id;
public SeqGenericTbl() {
}
public SeqGenericTbl(Long id) {
this.id = id;
}
#Id
#SequenceGenerator(name = "ENTITY_SEQ_GEN",
sequenceName = "SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ENTITY_SEQ_GEN")
#Column(name = "ID")
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you can get the Id from the entity of the sequence (persist it, you can rollback that tran or delete it or empty the related table later):
SessionFactory sf = HBUtil.getSessionFactory();
Session s = sf.openSession();
SeqGenericTbl sg=new SeqGenericTbl();
s.save(sg);
EntityClass entity1 = new EntityClass();
entity1.setEntityId(sg.getId());
//NOW YOU HAVE THE ID WITHOUT PERSISTING THE ENTITY
System.out.println(entity1.getEntityId());

Categories