I'm in a project which is based on Oracle DB and EclipseLink as EM implementation.
I got a table which has a standard id sequence generator, let's say:
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "INVOICE")
#TableGenerator(name = "INVOICE", allocationSize = 1, table = Constants.SEQUENCE_TABLE, schema = Constants.DATABASE_SCHEMA)
#Column(length = 40)
private String id;
I also got a unique numeric field. I've decided to create a special sequence for that field, and populate it through a trigger, which increments that numeric field on every insert, passing sequence "NextVal".
My question is: is it a good practice to take advantage of #Sequencegenerator and #TableGenerator annotation also for a field that is not an #Id for the same entity?
I'm not a trigger fan...so adding a thing like this from the hypothetical first snippet... i could manage whole entity with JPA standard Annotations:
#GeneratedValue(strategy = GenerationType.TABLE, generator = "UNIQUEFIELD")
#TableGenerator(name = "UNIQUEFIELD", allocationSize = 1)
#Column
private String uniqueValuesField;
Thanks!
JPA does not support sequences on non-id fields, so this isn't likely to work without relying or delving into your provider's native support.
You can always refresh the entity afterward if required to get the values set by triggers, but they are a common enough occurrence on legacy systems, so providers will have support of some form for triggers. EclipseLink has #ReturnInsert and #ReturnUpdate to allow getting the values back from triggers.
To mark a property as generated, use The Hibernate specific #Generated annotation.Properties marked as generated must additionally be non-insertable and non-updateable. Only #Version and #Basic types can be marked as generated.
http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#mapping-generated
never (the default)
the given property value is not generated within the database.
insert
the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like creationTimestamp fall into this category.
always
the property value is generated both on insert and on update.
hope this can help you.
I think writing a trigger is a redundant action, and it will reduce the readability of the code.
There are three main types of ID generators provided in hibernate TABLE, SEQUENCE and IDENTITY.
here is a good read with examples by Vladmihalcea on the Sequence generator strategies provided.
Or else you can create your own sequence generator where I had to keep a unique id among two tables so I did this
#Id
#GenericGenerator(name = "sequence", strategy = "IdGenerator")
#GeneratedValue(generator = "sequence")
#Column(name = "ID", columnDefinition = "BIGINT")
and in the Id generator
Public class IdGenerator extends IncrementGenerator {
private static long nextVal = Long.MIN_VALUE;
private static Object idlock = new Object();
private static volatile String COLUMN_NAME = "id";
#Override
public Serializable generate(SessionImplementor session, Object obj) {
if (obj == null) {
throw new HibernateException("Null object passed");
}
synchronized (idlock) {
nextVal = //get the value according to your logic
}
return nextVal;
}
}
Related
I have a situation in which I find the next sequence value (using nextval), set it to a database column and I want to reuse that value as the object's primary key. The problem is that although I triggger save with the correct values, Spring JPA generates another primary key and does not use the one I gave it.
For example, I have an entity:
public class MyEntity implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_MY_ENTITY")
#SequenceGenerator(sequenceName = "SEQ_MY_ENTITY", allocationSize = 1, name = "SEQ_MY_ENTITY")
private Long id;
#Column(name = "field1")
private String field1;
In some business flow, I need to set field1 = id. I do this by:
get next val from dual.
set value to field1
set value to id
Problem is that what is saved for id = field1 + 1. How can I have the two values in sync without calling save with empty field1 and then another save after I've updated field1?
You can use #Id on two columns : id and field1 or use composite key using #Embeddable
I want to implement one auto increment field other than id field that starts with 1 and increase by 1 sequentially.
Code Sample :
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id; //Id primary key
#Column(name = "request_number", nullable = false, unique = true, updatable = false, insertable = false)
#GeneratedValue(generator = "sequence", strategy = GenerationType.AUTO)
private Long requestNumber; //Talking about this
So, here requestNumber should increase automatically every time when ever object create. And that should increase sequentially.
Example : First entry's requestNumber will start with 1 and next requestNumber will be assign with 2 and so on...
I know it is possible via java code but I am looking for JPA provide such flexibility.
#GeneratedValue is used only for simple primary keys, as per the javadoc:
The GeneratedValue annotation may be applied to a primary key property or field of an entity or mapped superclass in conjunction with the Id annotation. The use of the GeneratedValue annotation is only required to be supported for simple primary keys. Use of the GeneratedValue annotation is not supported for derived primary keys.
If you want to do it in JPA you can define a #PrePersist method like:
#PrePersist
void doPrePersist() {
// Use EntityManager to create a native query
// read next value from a sequence
// set the field value
}
Another option would be to define the database column as IDENTITY but this will take care of auto incrementing outside of JPA e.g. the entity field won't be insertable and value won't be seen during entity persist operation.
Please note that SQL Server, and most databases, doesn't guarantee that there won't be gaps in the sequence. When a transaction that increments sequence is rolled back the value of the sequence is not, so you can end up with: 1, 2, 4, 5, 6, 10, ...
You have to declare a #SequenceGenerator annotation on your class (or field):
#SequenceGenerator(sequenceName = "MY_DB_SEQUENCE", name = "sequence")
public class MyClass {
// keep as is
}
Note that the generator = "sequence" on #GeneratedValue points to the #SequenceGenerator with the name = "sequence"
I'm migrating JPA api's like persist,save,merge,refresh,detach and remove to plain SQL using JDBC, where iam finding it hard to understand the concept of EntityManager.merge(someTask).
I tried a SQL update query for the merge API but the explanation of merge is as follows Merge the state of the given entity into the current persistence context.But with plain SQL and JDBC its hard to understand how to do the same and i need to handle OptimisticLock as well.
The entity class which is used for JPA is as follows.
#Entity
#Table(name = "TASK", indexes = {#Index(name = "RIO", columnList = "priority", unique = false),
#Index(name = "EXP", columnList = "expiry", unique = false),
#Index(name = "STA", columnList = "taskStatus", unique = false),
#Index(name = "CAT", columnList = "category", unique = false),
#Index(name = "NEXTTRY", columnList = "nextTry", unique = false)})
public class TaskEntity {
#Version
private int version;
#Basic
#Column(length = Integer.MAX_VALUE, columnDefinition = "varchar(" + Integer.MAX_VALUE + ")")
private String taskId;
#Basic
private String category;
#ElementCollection(fetch = FetchType.EAGER)
#MapKeyColumn(name = "KEY")
#CollectionTable(name = "TASKPROPERTIES", foreignKey = #ForeignKey(
name = "TASK_ID_FK",
foreignKeyDefinition = "FOREIGN KEY (TASKENTITY_ID) REFERENCES TASK (ID) ON DELETE CASCADE"))
#Column(length = Integer.MAX_VALUE, columnDefinition = "varchar(" + Integer.MAX_VALUE + ")")
private Map<String, String> TaskProperties;
#Basic
#Column(length = Integer.MAX_VALUE, columnDefinition = "varchar(" + Integer.MAX_VALUE + ")")
private String destination;
#Enumerated(EnumType.STRING)
private TaskStatus taskStatus;
#Basic
private String type;
#Basic
private Long expiry;
#Basic
private Long nextTry;
#Basic
private Integer retries;
#Basic
private Integer priority;
//Setters and Getters
//Equals and HashCode
}
Hence what would be the equivalent of EntityManger.merge(task) to SQL/HSQL.
Merge in essence is the process of merging an existing record in a table with what has been provided in the statement (i.e. UPDATE if the record exists else INSERT). Also known as UPSERT.
Let us say you have a table tbl_person that has primary key person_ssn and two other columns namely name and age. In case you want to insert a statement on a person_ssn that happens to exist there, DBs will throw error. Your requirement is to insert a record if the person_ssn doesn't exist else update the name and age. In such situation you will use Merge.
There are few ways to achieve this, two of them are
Issue at least two DML statements. First do a SELECT on the person_ssn and based on whether you found a record, subsequently, you will either issue an UPDATE or an INSERT statement
Use MERGE SQL statement. This is the more modern and direct way but not all databases support it. Read more information here. Further, just to get an idea, check here, on how MERGE SQL statement works in SQL Server which supports it
As far as Java JPA is concerned, implementations abstract this concept. Depending on DB's support for MERGE SQL statement, either it is used or two statements (SELECT followed by either UPDATE or INSERT) are issued to accomplish the same.
hsqldb offers MERGE SQL support as per comment provided.
There is more to merge semantically (in case of ORM context) other than just upsert. Essentially your entity model is a graph of objects having relations to each other using memory pointers. The objective of merge API is to enable reflecting the expected future state of object graph with the current state. Typically the ORM would issue SQL insert/updates/deletes to reflect the expected future state and not necessarily SQL MERGE. For e.g., the future entity state has a one to many relation as null - this would result in ORM issuing query to nullify the foreign key in the child table to reflect this state. In nutshell - when you pass an object ( which is a graph of interconnected objects) to merge , the ORM first determines for individual objects whether they need to be newly persisted or if they contain identifier of already persisted data then load them into persistence context ( if not already there) and apply all data changes and relationship updates. Finally the dirty checking mechanism of ORM makes sure to generate equivalent SQL to reflect this final state.
EntityManager - merge(T entity) Merge the state of the given entity into the
current persistence context.
I want one of the fields to be ignored when called save() method. The field is gonna get populated automatically by the database and returned. It should be treated as a read-only field.
I am concerned about private Timestamp ts; field:
#Entity
#Table(name = "time_series", schema = "ms")
#IdClass(Reading.class)
public class Reading implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "name", nullable = false)
private String sensorName;
#Id
#Column(name = "ts", insertable = false, updatable = false)
private Timestamp ts;
#Column(name = "reading")
private Double value;
...
As you see, I use insertable = false, updatable = false are inside the #Column annotation, so I'd expect that ts is ignored when forming the actual SQL behind the curtain.
#Override
#Transactional(readOnly = false)
public Reading save(Reading r) {
return readingRepository.save(r);
}
ReadingRepository is basically extended Spring's CrudRepository which has save(...) method.
When I save Reading object with ts=null I get an error from Postgres:
ERROR: null value in column "ts" violates not-null constraint
because Spring Data did not actually ignore the ts field based what I see from the log:
insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)
Clearly, I want the query to be without ts like this:
insert into ms.time_series (name, reading) values ('sensor1', 10.0)
Why is the field not being ignored?
Now if you ask me whether my database schema is okay I say yes. When I type SQL query in console without the ts everything is fine. I even tried #Generated and #GeneratedValue annotations. Name and ts are both forming a primary key for the table, however, the result is the same if I make only one of them a PK or if I add an extra surrogate ID column. Same result...
Am I overlooking something or is there maybe a bug in the Spring framework?? I am using Spring 5.1.2 and SpringData 2.1.2
Note: If I use #Transient annotation that persists the insert query correctly but then the field is being ignored completely even on read/fetch.
Many thanks for any help with this!
Try using GenericGenerator and GeneratedValue in your code.
Add the needed annotation and give values to all other members in Reading class, except ts.
Here some examples.
As you say
I get an error from Postgres
If you check the docs it states:
Technically, a primary key constraint is simply a combination of a unique constraint and a not-null constraint.
That's also true for multi-column primary keys (see here)
So, if ts is part of your primary key in the database (as the #Id indicates) it's simply not possible to insert null values in that column.
IMO Hibernate/Spring got nothing to do with that as
insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)
should be equivalent to
insert into ms.time_series (name, reading) values ('sensor1', 10.0)
I wanted to generate sequence using hibernate tool ( pojo to sql). And definitely it works fine.
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqid-gen")
#SequenceGenerator(name = "seqid-gen", sequenceName = "RTDS_ADSINPUT_SEQ" )
#Column(name="id")
public Long getId() {
return id;
}
This code generates below sql
create sequence RTDS_ADSINPUT_SEQ;
The problem is I wanted to specify properties like
START WITH, INCREMENT BY, NOCACHE
and the final ddl script should be some thing like below
CREATE SEQUENCE RTDS_ADSINPUT_SEQ MINVALUE 1 MAXVALUE
999999999999999999 INCREMENT BY 1 START WITH 1 NOCACHE;
But as far I saw hibernate only support name, sequncename, allocationSize, initialvalue. My doubt is, if we use allocationSize = 1 & initialValue = 1 do we still need to mention "nocache". If so, do we have any kind of annotation for "nocache"?
Please advice me if I can include that properties as annotation in the pojo.
sequence use only oracle, postgreSQL, DB2, H2
I know two case.
(1)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int idx;
Auto_increment, sequence object -> strategy = GenerationType.AUTO
(2)
Your case.
Element Detail
public abstract String name (Required) A unique generator name that
can be referenced by one or more classes to be the generator for
primary key values.
public abstract String sequenceName (Optional) The name of the
database sequence object from which to obtain primary key values.
Defaults to a provider-chosen value. Default: hibernate_sequence
public abstract int initialValue (Optional) The value from which the
sequence object is to start generating. Default:1
public abstract int allocationSize (Optional) The amount to increment
by when allocating sequence numbers from the sequence. Default:50
DDL
create sequence RTDS_ADSINPUT_SEQ start with 1 increment by 1;
Entity
#Entity
#SequenceGenerator(
name = "seqid-gen",
sequenceName = "RTDS_ADSINPUT_SEQ"
initiaValue = 1, allocationSize = 1)
public class XXX {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqid-gen")
private long id;
//getter, setter
}
As i know we do not have such annotation properties. But ideally, you should create the sequence using your own SQL query instead using the default one generated by hibernate. When the sequence existed in database, Hibernate will not generate one, you can also disable hibernate ddl generation.
We can generate sequence id using hibernate in below mentioned manner
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int uniqueid;