I'm working on a REST API using Spring. I have this class, which id's is being generated automatically:
#Entity
public class Seller implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private double tasa;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getTasa() {
return tasa;
}
public void setTasa(double tasa) {
this.tasa = tasa;
}
}
I added some endpoints to create, delete and get a seller from the DB. So my problem arises when I delete one seller from the DB. When I try to create a new one, I was expecting to get the lower available value for the id but what is actually doing is using some kind of counter/sequence. Let me show you:
So in my second post instruction I was expecting a json with id = 1, instead I received a 2. I tried using TABLE and IDENTITY strategies but the unwanted behavior continued. So my question is: how can I achieve the behavior I desire? I don´t want gaps between my seller's ids.
In general the database are designed to be incremental. When the ID is generated, it is not generated based on the content of the tables. instead of it, the ID is generated using a sequence. In your example you have some records, but imagine a database with a lot of records. The database generates the IDs based on a Sequence (or similar), to avoid read the data, an expensive process.
If the ID is not relevant to the business, then this behavior doesn't affect your process. (Like the message's id in a chat).
If the ID is important, I recommend to redefine the delete process. you probably need to preserve all the ids, like a customer id.
If you want to preserve the sequence and allow delete records, the recommendation is to generate the id by yourself, but you need to lead with problems like concurrence
I tried using TABLE and IDENTITY strategies but the unwanted behavior continued.
This is not unwanted behaviour. Check
How primary keys are generated.
So my question is: how can I achieve the behavior I desire? I don´t want gaps between my seller's ids
One way to achieve this is to not use #GeneratedValue(strategy = GenerationType.AUTO) and set id manually from program and in there you can put any logic you want.
It's not recommended to set primary key manually. If you want you can use any other field like seller_code for this behaviour.
Another question here which is similar to this.
Related
I'm very new to Spring/Springboot and have seen different approaches in tutorials regarding the model classes used to represent database objects. I was just wondering when it's appropriate to use which?
Approach 1:
A basic class to model a user object
public class User {
private final UUID id;
// other fields
public User(UUID id, <other fields>) {
this.id = id;
// set other fields
}
In the repository layer, we might have a DAO which looks something like
#Repository
public interface UserDao {
public int createUser(UUID id, <other fields>);
// other CRUD operations
}
When the user doesn't input a valid UUID (or absent) a default method could insert it by calling UUID.randomUUID()
Approach 2:
Instead of using a UUID as a unique identifier, instead, with something like Hibernate/JPA we use the #Entity annotation on the User class in the model package, and have the PK field annotated with #Id
#Entity
public class User {
#Id
private final long id;
// other fields
}
#Id annotation is the most commonly used approach in Hibernate. This will map a Java String / BigDecimal / long attribute to an identifier. And using this, you can use specify four generation strategies - AUTO, IDENTITY, SEQUENCE and TABLE.
UUIDs are used when you want your primary key to be globally unique. I can think of a few scenarios where you might want this -
You have data in multiple databases and your keys needs to be unique across different databases.
You need your generated id value even before you persist your record in your database for specific business purposes.
But the downside is that, UUIDs are long and may cost more in terms of storage space.
I have a Client entity with orgId and clientId as a composite key. When I have to insert a new client object, I have to generate clientId id sequentially for each orgId, so to do that, I am generating clientId by maintaining the last clientId of every orgId in a separate table, and selecting, adding 1, and updating it.
#Entity
#Table(name = "ftb_client")
public class Client implements Serializable {
#Id
#JoinColumn(name = "ORG_ID")
protected String orgId;
#Id
#Column(name = "CLIENT_ID")
protected int clientId;
#Column(name = "CLIENT_NAME_ENG")
private String clientNameEng;
//....
}
#Entity
#Table
public class MySeq implements Serializable {
#Id
protected String orgId;
private int lastClientId;
//....
}
public Long getNewClientId(String orgId) {
MySeq mySeq = getSession()
.createQuery("from MySeq where orgId = :orgId", MySeq.class)
.setParameter("orgId", orgId)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.uniqueResult();
mySeq.setLastClientId(mySeq.getLastClientId() + 1);
return mySeq.getLastClientId();
}
But, this leads to duplicate id generation if there are thousands of concurrent requests. So, to make it thread-safe I have to use Pessimistic locking, so that multiple requests do not generate the same clientId. But now, the problem is that lock doesn't get released until the transaction ends and concurrent requests keep pending for a long time.
So instead of using a lock, if I could use a separate sequence per orgId then I could make the id generation concurrent too. I want to manually execute the sequence generator by determining the sequence name in the runtime by doing something like client_sequence_[orgId] and execute it to generate the id.
And I also want to make it database-independent, or at least for Oracle, MySQL, and Postgres.
I want to know if it is possible or is there any other approach?
It doesn't matter if you use PESSIMISTIC_WRITE or not, a lock will be acquired anyway if you update the entity. The difference is that the lock is acquired eagerly in the case you describe here which prevents lost writes.
Usually, this is solved by creating a separate transaction for the sequence increment. To improve performance, you should increment by a batching factor i.e. 10 and keep 10 values in a queue in-memory to serve from. When the queue is empty, you ask for another 10 values etc.
Hibernate implements this behind the scenes with the org.hibernate.id.enhanced.TableGenerator along with org.hibernate.id.enhanced.PooledOptimizer. So if you know the sequences that you need upfront, I would recommend you use these tools for that purpose. You can also do something similar though yourself if you like.
I'm using Spring-data-Jpa where I've an entity
#Entity(name="person")
public class Person implements Serializable {
#javax.persistence.Id
private long dbId;
#Id
private final String id;
// others attributes removed
}
In above class I've two different ids id (marked with org.springframework.data.annotation.Id) and dbId(marked with javax.persistence.Id) , since my id field is always populated with a unique identifier (for Person class which I'm getting from somewhere else) so while using Spring JpaRepository it always tries to update the record and since it's not in db, nothing happens.
I've debug code and saw that it uses SimpleKeyValueRepository which gets the id field which is id, and thus it always gets a value and tries to update record, can I override this behavior to use dbId instead of id field? Is there any way to achieve same with some configuration or annotation, any help is greatly appreciated.
Each entity must have exactly one #Id. On the other hand, you might want to declare a column as unique. It can be done by:
#Entity(name="person")
public class Person implements Serializable {
#Id
private Long id;
#Column(unique = true)
private final String uuid;
// others attributes removed
}
Also remember, that Spring Data JPA id should be reference Long instead of a primitive as you want to save objects with id = null.
String id should probably be String uuid and be initialized as String uuid = UUID.randomUUID().toString();
Similar situation would be an unique email requirement for user. On one hand it'll be a primary key, but on the other, you won't mark it as #Id.
If you need further clarification or your environment is more complicated, just ask in comments section below.
I have used both the ways of mapping _id as described in the Spring Docs here.
using #Id annotation
having a field with name id without any annotation
in my previous project where we used MongoDB as database and Spring Data for DAO operations. It worked without any problem for both String a well as for BigInteger.
Now we are using DocumentDB with MongoDB API(as Spring Data does not support DocumentDB).
I am able to use all the Spring Data methods, but I am not able to use custom id.
Below is my entity:
public class S{
private String id;
/* other fields here */
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/* getters and setters for other fields */
}
This is the DAO:
public interface SDao extends MongoRepository<S, String> {
}
Now if anywhere in my code I do:
s = new S();
s.setId("some-id-here");
The record gets successfully persisted in the DB with custom id some-id-here as String (not ObjectId), but after that it throws ClassCastException saying Long cannot be converted to Integer.
Same is the case when using BigInteger for id.
If I am not setting the custom id, i.e. I comment the setting of id as below:
s = new S();
// s.setId("some-id-here");
no exception is being thrown, but the record is being persisted with a random id provided by database itself as ObjectcId.
I want to save the record with custom id, so that I can easily update it when needed.
Currently if I have to update a record, I need to retrieve it using a key which is not mapped to _id and then update it and then delete the old record from the DB and then persist the updated one, which I feel is absolutely inefficient as I am not able to make use of _id.
My question is why am I getting ClassCastException, that too mentioning Conversion of Long to Integer
Is DocumentDB internally doing some conversion which is throwing this exception. If yes, how to tackle it? Is this a bug?
One alternative could be to let DocumentDB/ MongoDB create those IDs for you by default. In your class you can have another field which can serve as natural ID and create a unique index on that field for fetch optimization.
Refer https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/ for indexes.
The id generation rules are explained here link
I am attempting to implement a Hibernate/JPA2 solution over an existing schema, which cannot be changed. Here is a minimal example of the existing schema:
CREATE TABLE REASON (
REASON_CODE CHAR(1),
REASON_DESCRIPTION CHAR(50))
CREATE TABLE HEADER (
REASON_CODE CHAR(1),
OTHERFIELD1 CHAR(40),
OTHERFIELD2 CHAR(40) )
Normally this would be the "correct" way from a DB perspective: Link REASON to HEADER by the REASON_CODE. However it's presenting me with an awkward problem in Java and I'm not sure of the best way to solve it. I've modeled these entities as follows:
#Entity
#Table(name="REASON")
public class Reason implements java.io.Serializable {
#Id
#Column(name="REASON_CODE", unique=true, nullable=false, length=1)
private Character reasonCode;
#Column(name="REASON_DESCRIPTION", nullable=false, length=25)
private String reasonDescription;
}
#Entity
#Table(name="HEADER")
public class Header implements java.io.Serializable {
#ManyToOne
#JoinColumn(name = "REASON_CODE", nullable = false)
private Reason reason;
#Column(name="OTHERFIELD1")
private String otherField1;
#Column(name="OTHERFIELD2")
private String otherField2;
}
Once again, as far as I can tell, this is "correct" from a Java perspective - linking Header to Reason with a reference.
The problem is that when I need to use one of these Reason values in my code I wind up with awkward syntax like:
Reason r = reasonService.findOne('X'); // X is the REASON_CODE in the database record
// Do some processing with variable r
Or this:
header.setReason(reasonService.findOne('X'));
Ideally I could implement Reason as an enum like:
public enum Reason {
X_MARKSTHESPOT("X"),
C_MEANSSOMETHINGELSE("C"),
F_MEANSATHIRDTHING("F") ;
private String code;
private Reason(String code) {
this.code = code;
}
}
And then simply have this in my code:
header.setReason(Reason.X_MARKSTHESPOT);
But from what I understand that is not possible with JPA, which offers only EnumType.STRING (basically the name) or EnumType.ORDINAL (even worse, the index in the enum list). A possible way around this would be JPA 2.1's Converter, but I have never used it. I have also read here (in one of the answers) that a Hibernate User Type might be useful. One of our programmers has solved this in another app by writing two complete classes - an enum class for internal use and a "shadow" class which iterates through the enum and syncs the records in the database on every startup. But this seems like a kludgey way to do it. What is the best way to handle this, bearing in mind that the database schema cannot be changed?