I have parent and child entities with IDs that are generated using the strategy GenerationType.TABLE because I'm working with a MySQL database.
If I create the parent without specifying an ID (i.e., create the parent for the first time), add new children to it, and save the parent, then hibernate works as expected and uses the MySQL AUTO_INCREMENT column feature.
However, If I create a parent and specify an ID (i.e., instantiating a parent that has already been persisted), add a child to it, and save the child, then hibernate issues a select sequence_next_hi_value from hibernate_sequences ... and uses that as the child's PK.
Likewise, if I instantiate a parent by getting it from the database via session.get(Parent, 1), add a new child to it, and save either the parent or the child, then hibernate uses sequences to get the child's PK.
If I create enough new parents (32767, to be exact) to run up the mysql AUTO_INCREMENT counter, there would be failures due to the primary key not being unique enough.
Here are my parent and child entities (named Location and Category, respectively):
#Entity(name="location")
public class Location {
#Id #GeneratedValue(strategy=GenerationType.TABLE) #Column(name="location_id")
private int id;
#OneToMany(mappedBy="location")
#Cascade(CascadeType.SAVE_UPDATE)
private List<Category> categories;
...
}
#Entity(name="category")
public class Category {
#ID #GeneratedValue(strategy=GenerationType.TABLE) #Column(name="category_id")
private int id;
#ManyToOne #JoinColumn(name="location_id")
private Location location;
}
Here is the hibernate code:
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'category' for update
Hibernate: update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'category'
Hibernate: insert into category (location_id, category_id) values (?, ?)
Beforehand: I have no answer why hibernate uses another strategy when persisting your child record. But I may give you some answers or hints about the other issues you described:
Regarding the strategy GenerationType.TABLE, your application relies on the database functionality of generating an ID. This may cause database round-trips for each new database record inserted. When using an alternative strategy like hi-lo, the generator will reserve a bunch of ids (for example 100 or 1000) by contacting the database only once. This may improve performance greatly. On the other side, you may loose some ID when the application is restarted. If you may life with this issue hi-lo should be considered.
Another benefit of using a strategy not relying on a database product is, that if you switch databases you may keep your mappings.
Related
I have an owning entity class with some associated entities, e.g.
#Entity
public class Parent {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "parentId", cascade = CascadeType.ALL)
List<Child> children;
...
}
#Entity
public class Child {
#Id
#GeneratedValue
private UUID id;
private UUID parentId;
...
}
I am using Spring Boot Starter Data JPA. I traced the code to the SimpleJpaRepository class, I noticed that on calling save(parent), it checks if isNew() returns true, Spring will call persist(); otherwise it calls merge(). This makes total sense, as persist() will generate only an INSERT, while merge() will generate a SELECT (if it hasn't done before) and then followed by an INSERT if the SELECT returns nothing; otherwise an UPDATE.
The above works well when saving a new Parent with new Child, only INSERTs are generated without any SELECT.
However, my problem is, when creating some new Child and adding them to an existing Parent, then on saving the parent, somehow I noticed Spring JPA is still generating an extra SELECT for each of these new Child entities before the INSERTs, which I found unnecessary.
Is there a way to avoid these SELECT queries?
Further investigation I found that if I leave the id of the Child null (i.e. let it auto generates a new id), then only INSERT is generated. However, if I manually assign an id to a Child, then a SELECT will be generated before an INSERT. Is there a way I can assign id to Child while avoiding the extra SELECT?
On the basis of your code i think because of OnetoMany mapping and you did't provide cascade type then it not happen
select query running two times
provide more information
full code of Entity classes
properties file
I want to rewrite the call delete operation (on association table) on a many-to-many association sending by EclipseLink when we use only java code.
Let me explain the goal.
I have 3 tables, person, unit and an associative one : PerInUnit, so a person can be in multiple units and a units can contains many people. But I have some dependances on the PeInUnit table (If the person was present or not on a specific date, another table (Participations)), so I can't (and I don't want) delete a record. For that, I make softs deletes, so I can keep records to make some statistics.
I read already about the Customizer and AdditionalCriteria and I setted them to the PerInUnit class. It works perfectly => when I make an em.remove(myPerInUnit); the sql query sent to the db is Update PER_IN_UNIT SET STATUS='delete' WHERE id = #id; and the specified row as "delete" for status. Also, when I read all records, I don't have them with status "delete". But I use explicitly the PeeInUnit class.
Here is the code :
#Entity
#Table(name = "PER_IN_UNIT")
#AdditionalCriteria("this.status is null")
#Customizer(PIUCustomizer.class)
public class PerInUnit implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GEN_SEQ_PIU")
#SequenceGenerator(name = "GEN_SEQ_PIU", sequenceName = "SEQ_PIU", initialValue = 1, allocationSize = 1)
#Column(name = "ID")
private Long id;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "PER_ID")
private Person person;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "UNI_ID")
private Unit unit;
#Column(name = "STATUS")
private String status;
//Constructor, getters, setters
}
And the code for the PIUCustomizer :
public class PIUCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getQueryManager().setDeleteSQLString("UPDATE PER_IN_UNIT SET STATUS = 'delete' WHERE ID = #ID");
}
}
Here come the problem : As I use EclipseLink with bidirectionnal relationship I want to make some instruction like myUnit.getPeople.remove(currentPerson); (remove the current person from the unit "myUnit"). But EclipseLink sent the following instruction (during commit !) :
DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))
instead of the
Update PER_IN_UNIT SET STATUS='delete' WHERE ((UNI_ID = ?) AND (PER_ID = ?))
that I expected and raise (obviously, because of dependances (FKs)) the following exception :
Query: DataModifyQuery(sql="DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))")
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:157)
at test.Crud.update(Crud.java:116)
at test.Test.runTest(Test.java:96)
at test.Test.main(Test.java:106)
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-02292: integrity constraint (PEOPLE.FK_PAR_PIU) violated - child record found
Other problem (in the same kind), when I make something like System.out.prinln(myUnit.getPeople()) I have all the people in the unit "myUnit", including them having status 'delete'.
Is it possible to change some code/instructions/Customizer/etc in eclipseLink to change the delete call from person for PerInunit table, or I have to make my own queries and use them instead of using powerful of orm ?
Thanks for your answers and please forgive me for my poor english !
Fab
You should not be getting a delete when you call myUnit.getPeople.remove(currentPerson) unless you mapped Unit to Person with a ManyToMany using the PER_IN_UNIT table. Since you have an entity for the PER_IN_UNIT table, this would be wrong, as it really should be a Unit-> PerInUnit OneToMany mapping and then a PerInUnit -> Person ManyToOne mapping. The myUnit.getPeople.remove(currentPerson) call would then simply be getting the PerInUnit instance and marking its status as deleted, or dereferencing it and letting JPA call remove, thereby using your soft delete SQL query.
By using a ManyToMany mapping for the PER_IN_UNIT table, this mapping is completely independent to your PerInUnit entity mapping, and knows nothing about the entities that maybe cached or the soft deletes required to remove them. If you don't want to map the PER_IN_UNIT table as an entity, see http://www.eclipse.org/forums/index.php/t/243467/ which shows how to configure a ManyToMany mapping for soft deletes.
In my spring project, the tables in database are created automatically by Hibernate using my entity classes as base, but I insert some default values in the table manually (using pgAdmin3).
Because that, I am facing now this problem: when I try insert a value via Java code in one of the tables which already have values, I receive a error message, saying the primary key already exists in the database.
Anyone knows how to solve this problem?
UPDATE
That's how I declare my primary key in my class:
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Call this SQL query once per table to set the sequence to the next free number:
SELECT setval('tblname_id_seq', max(id)) FROM tblname;
tblname being the actual name of the table.
Hibernate may use a different naming convention, or the sequence may have been renamed. If you can't find the sequence behind the serial column, check with (per documentation):
SELECT pg_get_serial_sequence(tblname, column_name)
More details:
Modify Django AutoField start value
How to import a CSV to postgresql that already has ID's assigned?
The problem here might be that you declare the id as a primitive instead of a wrapper.
So instead of:
private int id;
You should have:
private Integer id;
When you create the entity with the id is initialized as 0, instead of NULL.
That's why you get duplicate id constraint violation exceptions.
Only when the id is NULL the AUTO generation strategy will delegate the id assignment to the database.
What is the best practice for defining table indexes for entities supporting optimistic locking?
For sure, entity id has to be part of some index in DB to enable fast lookups by id. What about version column? Does it make sense to make it part of an index?
What if we do not define DB primary key, but create an index consisting of entity id + version column? Any risk of having two rows in DB with same entity id? Say two transactions persist two entities with the same entity id in parallel?
Suppose you have an entity defined with a version column as follows:
#Entity
public class MyEntity implements Serializable {
#Id
#GeneratedValue
private Long id;
private String name;
#Version
private Long version;
//...
}
On update, the field annotated with #Version will be incremented and added to the WHERE clause, for example:
UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))
As you can see, the version column is used in the WHERE clause, but so is the ID column (which has an index on it already as it is the primary key), so I don't think you would see much benefit in adding an index to the VERSION column.
I have the following existing DB schema, which I'd like to recreate with Java and plain JPA annotations (using hibernate as provider, so hibernate specific annotations would work as a last resort):
CREATE TABLE users (
user_id NUMBER NOT NULL -- pk
);
CREATE TABLE userdata_keys (
userdata_key_id NUMBER NOT NULL, -- pk
key VARCHAR2(128) NOT NULL
);
CREATE TABLE users_userdata (
user_id NUMBER NOT NULL, -- fk users.user_id
userdata_key_id NUMBER NOT NULL, -- fk userdata_keys.userdata_key_id
value VARCHAR2(256)
);
I've thus created the following classes and annotations:
class User {
#Id
Long id;
#OneToMany
Set<Userdata> userdata;
}
class UserdataKey {
#Id
Long id;
String key;
}
class Userdata {
String value;
#EmbeddedId
UserdataId userdataId;
}
#Embeddable
class UserdataId {
User user;
UserdataKey userdataKey;
}
I left out columnName attributes and other attributes of the entities here.
It does however not quite work as intended. If I do not specify a mappedBy attribute for User.userdata, hibernate will automatically create a table USERS_USERS_USERDATA, but as far as I've seen does not use it. It does however use the table which I specified for the Userdata class.
Since I'm rather new to Java and hibernate as well, all I do to test this currently is looking at the DB schema hibernate creates when persisting a few sample entries.
As a result, I'm entirely puzzled as to whether I'm doing this the right way at all. I read the hibernate documentation and quite a bunch of Google results, but none of them seemed to deal with what I want to do (composite key with "subclasses" with their own primary key).
The mappedBy attribute is mandatory at one of the sides of every bidirectional association. When the association is a one-to-many, the mappedBy attribute is placed ot the one- side (i.e. on the User's userdata field in your case).
That's because when an association is bidirectional, one side of the association is always the inverse of the other, so there's no need to tell twice to Hibernate how the association is mapped (i.e. which join column or join table to use).
If you're ready to recreate the schema, I would do it right (and easier), and use a surrogate auto-generated key in users_userdata rather than a composite one. This will be much easier to handle, in all the layers of your application.