Generate sequence NexVal without execute select query - java

My class is not Entity there is code fragment
#SequenceGenerator(name="seqUniqueKeyGenerator",sequenceName="SEQ_UNIQUE_KEY",allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="seqUniqueKeyGenerator")
#Id
private Integer sequenceId;
public Integer getSequenceId() {
return sequenceId;
}
public void setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
}
public static void main(String[] args) {
UniqueKeyGenerator uniqueKeyGenerator = new UniqueKeyGenerator();
System.out.println(uniqueKeyGenerator.getSequenceId());
}
I want retrieve nextVal like this, is it possible?

You can consume nextVal as mentioned in this thread but you have to consider it is consumed by means of a SQL sentence, which means this is a solution coupled to database.
I don't know a way to consume nextVal in such way you are asking above.

We all know the default behaviour of Hibernate when using #SequenceGenerator - it increases real database sequence by one, multiple this value by 50 (default allocationSize value) - and then uses this value as entity ID.
From G. Demecki
This means that the ID id not generated in your Java-Program. It is created in your database. So you can't read it before it is acutally generated in the database. You can guess it using the formula described by G.Demecki but that is certainly not the way to go.
If you want the id of an entity just save it and read the id from the return value of save what should be the saved entity itself.

Related

Entity with oracle sequence as ID throws org.hibernate.PropertyAccessException

When I try to save my entity in my oracle db using the .save() method of the hibernate session I get this exception :
org.hibernate.PropertyAccessException: Could not set field value [1757] value by reflection : [class mypackage.MyEntity.myid] setter of mypackage.MyEntity.myid
The value 1757 is correct, it's the one I want for my ID. (when I do a select myschema.mysequence from dual; in my DB I get the next value of this one so it seems to work properly)
I don't understand the error, it's like it doesn't find the setter method but when I explore the exception, in the cause it says something else :
Can not set java.lang.Long field mypackage.MyEntity.myid to null value
which, for me, doesn't make any sense since the value "1757" is not null at all obviously ^^, and Long should accept null values anyway so I have no idea why it doesn't work. I supposed it was because an ID column coulnd't be null (which is false since we also can have a default for null values). So really, I don't get it.
My entity :
#Entity
#Table(name = "MYTABLE")
public class MyEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="seqName", sequenceName="myschema.mysequence",allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seqName")
#Column(name = "MY_ID")
private Long myid; //NUMBER
//some other column
public Long getMyid() {
return myid;
}
public void setMyid(Long myid) {
this.myid = myid;
}
//some other accessors
}
I tried to change the type "Long" to long and I tried to do some other setters with different types (long, int, BigInteger, Integer, BigDecimal...) but nothing changed.
I'm able to create an occurrence using the "createNativeQuery" method with "myschema.mysequence.nextval" in myid field like this :
session.createNativeQuery("INSERT INTO myschema.MYTABLE (MY_ID, someotherfields)"
+ " VALUES (myschema.mysequence.nextval, someotherfieldvalues)")
.executeUpdate();
but if I do so, the id is lost... I want to get it back from the query like it's supposed to be with the session.save() method.
For now, the only way I found to get my ID from sequence after insert statement was to create it before (SELECT myschema.mysequence.nextval FROM DUAL) and it's terrible for the performances. We often add rows by thousands.
Try adding a no-parameter constructor to your entity.
Did you try debugging to the point where the exception is initially thrown and inspect what is happening exactly? Maybe some underlying exception is swallowed. Are you sure you updated Hibernate to the latest version 5.4.34/5.5.7? If so, and you still have the problem, please create an issue in the issue tracker(https://hibernate.atlassian.net) with a test case(https://github.com/hibernate/hibernate-test-case-templates/blob/master/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java) that reproduces the issue.

Tell Hibernate to only let the Database generate a value when it's null

I'm implementing the PUT Methods for a REST-API.
I have a POJO similar to the following:
public class Brand implements Serializable {
#Column(columnDefinition = "serial")
#Generated(GenerationTime.INSERT)
#JsonIgnore
private Integer id;
#Id
#JsonProperty("brand")
private String brand;
.
.
.
}
Within the postgresql database the brand table has the following columns:
a database-internal id (SERIAL) which shall not be visible to the outside.(This is used manly for joining tables)
a brand (TEXT) which is the primary key
My Service method looks like this:
public Brand updateBrand(String brand, Brand update) {
Brand b = brandRepository.findBrandByBrand(brand);
if(b == null) { //If not exists create new one
b = new Brand(null, brand);
}
else { //If exists keep id, delete old one and create new entry
if(update != null && update.getBrand() != null) {
brandRepository.delete(b);
}
ServiceUtils.copyProperties(update, b); //This is within the if clause, because brand is the only value
}
return brandRepository.save(b);
}
And the controller would have something like this:
#PutMapping(value = "/brand/{brand}")
public ResponseEntity<Brand> updateBrand(#PathVariable("brand") String brand,
#RequestBody Brand update) {
Brand updated = articleNumberService.updateBrand(brand, update);
if(updated == null) {
throw new EntryCreationFailedException(brand); //self made exception
}
return new ResponseEntity<>(updated, HttpStatus.OK);
}
Now my following problem is, that when calling PUT ../brand/stackoverflow
with body:
{"brand":"StackOverflow")
it deletes the old stackoverflow brand (which had id=1, for example) and creates a new one called StackOverflow. But when checking the database the id column is incremented (so now it has id=2).
I checked and this is caused by hibernate still calling:
insert
into
brand
(brand)
values
(?)
This definitly is what I want when id is null. Which happens when creating a new Brand for example. But when only overriding the brand and id is not null I want hibernate to call this:
insert
into
brand
(id, brand)
values
(?, ?)
I know this would be possible by creating a own save method and in an emergency override the query. BUT I'm quite optimistic that this should be possible without. Bt I can't realy find fitting answers to this. I already had problems finding the proper annotations for the postgresql-specific serial behavior.
P.S: I know that some will shout "why would you have Brand as the primary key and not id!?" But this only is a simple class/part of the database. There are more complex classes which use exactly the same way for the internal database id, (and actually need it), but have multiple primary keys etc. So this rather is a very simple representation for my problem.
Since the Postgres use the serial as the auto_increment, it's not included the id insertion in sql statement. Behind the scene, it create the sequence for it. So you should use the Generation type IDENTITY for it.
If it was a field with #Id you would be able to write a custom #GenericGenerator that fetched the sequence if the value was null, but as it's not a primary key, I think you would end up having to have a separate parent Entity with its own generated Id.

What's the proper way of generating a unique field using Spring Boot / JPA?

First of all, I'm not taking about the primary id of the record. I'm talking about an field that is used by users to identify the record that's automatically generated but changeable by the user, not sequential and not a UUID. For example, starting with an account entity:
#Entity
#Data
class Account {
#Id
#GeneratedValue
private int id;
#Column(unique=true)
#NotNull
private String slug;
#Column
private String name;
}
and then I simply create a record:
#Autowired
private AccountRepository accountRepository;
Account account = new Account();
account.setName("ACME");
accountRepository.saveAndFlush(account);
At that point, the slug should have been generated, either completely randomly, or by doing something based on the name. How should that be done?
I know without locking the whole table it's impossible to ensure that the insertion won't result in an exception due to the uniqueness constrain being violated. I'm actually OK blocking the whole table or even letting the exception happen (you need a lot of requests per second fora conflict to happen between the check for availability and the insert).
If you separate the slug from the Account table and put it in a (id, slug) table by itself, you can generate the slug first (retrying until you succeed) and then persist the Account with a link to the just generated slug id.
You can't achieve this in a #PrePersist method, so your service needs to create the slug whenever you're creating an new Account. However it does simplify things on the application side (e.g. you don't need to wonder which constraint was violated when persisting an Account).
Depending on your other code, you can also get around locking the Account table and even the Slug table if you go for the optimistic approach.
A pseudo-code example of a service method that creates a new account (providing new Slug() creates the random slug):
#Autowired SlugRepository slugRepository;
#Autowired AccountRepository accountRepository;
public void createAccount(Account a) {
Slug s = null;
while(s == null) {
try {
s = slugRepository.save(new Slug());
} catch(Exception e) {
}
}
a.setSlug(s);
accountRepository.save(a);
}
I can think of JPA callbacks to generate the slug. In your case #PrePersist could be useful.
That said, why you need to make sure the value is available with a select before inserting the record, so the window for a collision to occur is tiny? You do have unique constraint on the column, right?
Update
Personally I would prefer to address it like this:
Use JPA callback #PrePersist when generating the the slug. Use to random UUID or timestamp to minimise the possibility of collision. No checking for collision as chances are minimal.
When updating the Account for user generated slug, always check first using query for collision. This check will offcourse happen in service update method itself.
This way I can be DB agnostic and also don't have to use repository/service in entity or listener classes.
I will do something like a separate Bean, helper or service class like this.
public class SlugService {
public String generateSlug(String slug)
{
if (accountRepo.getBySlug(slug) != null){ //check if it is already
return slug
} else {
slug.append("-"); //whatever the syntax
generateSlug();
}
}
public String makeSlug()
{
String slug = split by " ", replace by "_"(accountObject.getName);
generateSlug(slug)
}
}
Call the makeSlug(); method.

Need to know if each field has changed, how should I model this in Hibernate

So I have a class with three fields that maps to a table using hibernate
Class Widget
{
String field1;
String field2;
String field3;
}
On application startup a number of instances these widgets will be added to the database from an external files, but when I exit the application I need to know which (if any) of these fields have been changed by the user since the application was started, so the changes can be saved back to the files. I also need to store the original value for logging purposes.
I can't work whether I need a status field in the table or whether there is already a way of doing this using Hibernate/Database.
EDIT:A good solution to the program was given below . however the main reason I am using Hibernate is to reduce memory consumption so storing the original values when changed is not a good solution for me , I want everthing stored in the database. So I have create this new question How do I store a copy of each entity I add to database in Hibernate
Given an entity like the following you can track changes on one of it's field (while preserving its original value too).
#Entity
#Table(schema = "test", name = "test")
public final class Test {
private static final int ORIGINAL = 0;
private static final int CURRENT = 1;
private Integer id;
// holds the original and current state of the field
private final AtomicReferenceArray<String> field = new AtomicReferenceArray<>(2);
#Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Transient
public String getOriginalField() {
return field.get(ORIGINAL);
}
#Basic
public String getField() {
return field.get(CURRENT);
}
public void setField(String field) {
this.field.compareAndSet(ORIGINAL, null, field);
this.field.set(CURRENT, field);
}
#PreUpdate
public void preUpdate() {
System.out.format("Original: %s, New: %s\n", getOriginalField(), getField());
}
...
}
If there is a single row in a database like this:
id: 1
field: a
version: 2011-12-02 11:24:00
before the field gets updated (say, from a to b) you'll get the following output.
Original: d, New: b
The original value gets preserved even if the the entity is updated multiple times and both state can be accessed through the corresponding getters (getField and getOriginalField—you can get more creative than me in the naming :).
This way, you can spare yourself from creating version columns in your database and also can hide the implementation details from clients.
Instead of an AtomicReferenceArray you could use arrays, lists, etc, to track all changes like this way.
The #PreUpdate isn't necessary of course, but this way you can be notified of changes in the entity's state and atomically save the updated fields into file. There more annotations like these: see the documentation for javax.persistence for other annotation types.
If you are using MySql then you can get table's last update time from information_schema database like
SELECT UPDATE_TIME FROM `information_schema`.`tables`
WHERE TABLE_SCHEMA = 'dbName' AND TABLE_NAME = 'tableName'
Or else simple solution will be to add a column for update time stamp. By this you can even monitor which particular row has been updated.
If you need to synchronize with files as soon as you save into database, You can use the Hibernate event mechanism to intercept any save to database and save it to file, here's a sample doing that.

Automatically apply field conversion function in Hibernate

I have a database table with a field that I need to read from and write to via Hibernate. It is string field, but the contents are encrypted. And for various reasons (e.g. a need to sort the plain text values), the encrypt/decrypt functions are implemented inside the database, not in Java.
The problem I'm struggling with now is finding a way to invoke the encrypt/decrypt functions in Hibernate-generated SQL everywhere that the field is referenced and in a way that's transparent to my application code. Is this possible? I've looked into Hibernate's support for "derived" properties, but unfortunately, that approach doesn't support read-write fields. Any ideas appreciated.
I don't think there's a way to make encryption like you've described it completely transparent to your application. The closest thing you can get is to make it transparent outside of entity. In your entity class:
#Entity
#SQLInsert(sql="INSERT INTO my_table(my_column, id) VALUES(encrypt(?),?)")
#SQLUpdate( sql="UPDATE my_table SET my_column = encrypt(?) WHERE id = ?")
public class MyEntity {
private String myValue;
....
#Formula("decrypt(my_column)")
public String getValue() {
return myValue;
}
public void setValue(String value) {
myValue = value;
}
#Column (name="my_column")
private String getValueCopy() {
return myValue;
}
private void setValueCopy(String value) {
}
}
value is mapped as derived property, you should be able to use it in queries.
valueCopy is private and is used to get around derived property being read-only.
SQLInsert and SQLUpdate is black voodoo magic to force encryption on insert / update. Note that parameter order IS important, you need to find out what order Hibernate would generate parameters in without using custom insert / update and then replicate it.
You could have a trigger internal to the database that, on retrieval, decrypts the value and replaces the returned result and on insert encrypts the value and replaces the stored result with the encrypted value. You could also do this with a view wrapper - i.e. have an insert trigger on the view, and have the view automatically decrypt the value.
To better explain: have a view that decrypts the value, and an on insert trigger that encrypts the value that is linked to the view.
Actually, in the end, I went a different route and submitted a patch to Hibernate. It was committed to trunk last week and so I think it will be in the next release following 3.5. Now, in property mappings, you can specify SQL "read" and "write" expressions to call SQL functions or perform some other kind of database-side conversion.
Assuming you have access to the encrypt/decrypt algorithm from within Java, I would set up my mapped class something like
public class encryptedTable {
#Column(name="encrypted_field")
private String encryptedValue;
#Transient
private String value;
public String getEncryptedValue() {
return encryptedValue;
}
public String getValue() {
return value;
}
public void setEncryptedValue(String encryptedValue) {
this.encryptedValue = encryptedValue;
this.value = decrypt(encryptedValue);
}
public void setValue(String value) {
this.value = value;
this.encryptedValue = encrypt(value);
}
}
And then use get/set Value as the accessor within your program and leave the get/set EncryptedValue for Hibernates use when accessing the database.
Why not just use the SQl server encryption that seems to already be in place by calling a stored proc in Hibernate instead of letting Hibernate generate a query?

Categories

Resources