Meaning of #GeneratedValue with strategy of TABLE - jpa

The JPA specification gives the following explanation of the annotation #GeneratedValue(strategy=TABLE):
The TABLE generator type value indicates that the persistence provider must assign primary keys for the entity using an underlying database table to ensure uniqueness.
But what does "using an underlying database table" mean in practice? Does it mean using an auxiliary table? Or by scanning the entity-table to find an ID not in use? Or something else?

Check out JavaDoc for TableGenerator, it has a nice example of how it works:
Example 1:
#Entity public class Employee {
...
#TableGenerator(
name="empGen",
table="ID_GEN",
pkColumnName="GEN_KEY",
valueColumnName="GEN_VALUE",
pkColumnValue="EMP_ID",
allocationSize=1)
#Id
#GeneratedValue(strategy=TABLE, generator="empGen")
int id;
...
}
Example 2:
#Entity public class Address {
...
#TableGenerator(
name="addressGen",
table="ID_GEN",
pkColumnName="GEN_KEY",
valueColumnName="GEN_VALUE",
pkColumnValue="ADDR_ID")
#Id
#GeneratedValue(strategy=TABLE, generator="addressGen")
int id;
...
}
Basically ID_GEN is an internal (non-business) table of key-value pairs. Every time JPA wants to generate ID it queries that database:
SELECT GEN_VALUE
FROM ID_GEN
WHERE GEN_KEY = ...
and incremenets the GEN_VALUE column. This mechanism can be used to emulate sequences or to take even further control of generated ids.

In the case of EclipseLink, it uses an auxiliary table. The documentation says
By default, EclipseLink chooses the TABLE strategy using a table named SEQUENCE, with SEQ_NAME and SEQ_COUNT columns

Related

JPQL by table name instead of entity name

I have a new table in my DB which simply aggregates some data which I don't want to hold as a reference in any of my entities. Because of above I don't have an entity of that type in my application. Now the problems starts when I want to create simple query from that table. We use named queries and criteria api in our application as a standard. Since my query is simple and is going to be asked a lot of times the perfect solution would be to use JPQL and named query. But how might I achieve this without having an entity related to this table... As far as I remember there is COLUMN('xxx') expression which can be used to obtain column which isn't used in entity, so maybe there is something similar to do the trick with whole table?
So let me recap, because It's possible that You may have a problem to understand me well :)
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
#Entity
public class Pet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Let's say that besides above two tables I have third one which I don't want to use neither in Pet nor in Person. So:
CREATE TABLE ANY_OTHER_TABLE(
PERSON_ID BIGINT NOT NULL,
PET_ID BIGINT NOT NULL
);
So now question is how should JPQL look like to produce SQL like this:
SELECT * FROM ANY_OTHER_TABLE WHERE PET_ID = 1;

Why does EclipseLink support the #CascadeOnDelete with #ElementCollection?

I have the following embeddable class that contains an #Lob:
#Embeddable
public class EntityState {
private Integer version;
#Lob
#XmlJavaTypeAdapter(CharArrayAdapter.class)
private char[] xmlState;
...
}
I also have the following embeddable class that contains the above embeddable:
#Embeddable
public class EntityEvent {
#NotNull
private String note;
private EntityState entityState;
...
}
Finally, I have many entity classes that contain a property called history that is a list of EntityEvents. The following is an example:
#Entity
public class Company {
#NotNull
#ElementCollection
private List<EntityEvent> history;
...
}
When I deploy my application in GlassFish 4.1, EclipseLink creates the following tables in my Derby 10.11.1.1 database:
COMPANY
COMPANY_HISTORY
When I create a new Company, my application creates an EntityEvent and adds the EntityEvent to the Company history.
When I modify a Company, my application does the following:
Creates an EntityState object and sets the xmlState property to an XML representation of the unmodified entity.
Creates an EntityEvent object containing the above EntityState.
Adds the EntityEvent to the Company history.
The problem is that when I try to delete an entity that has a history with multiple EntityEvents I receive the following error:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLSyntaxErrorException: Comparisons between 'CLOB (UCS_BASIC)' and 'CLOB (UCS_BASIC)' are not supported. Types must be comparable. String types must also have matching collation. If collation does not match, a possible solution is to cast operands to force them to the default collation (e.g. SELECT tablename FROM sys.systables WHERE CAST(tablename AS VARCHAR(128)) = 'T1')
Error Code: 20000 Call: DELETE FROM Company_HISTORY WHERE ((((((((((CHANGES = ?) AND (CLIENTTYPE = ?)) AND (CREATED = ?)) AND (IPADDRESS = ?)) AND (NOTE = ?)) AND (TYPE = ?)) AND (VERSION = ?)) AND (XMLSTATE = ?)) AND (CREATER_ID = ?)) AND (Company_ID = ?)) bind => [10 parameters bound]
I found a few references to the issue in the following links:
Hibernate - #ElementCollection - Strange delete/insert behavior
http://eclipse.1072660.n5.nabble.com/Customizing-delete-calls-before-updating-a-ElementCollection-td7312.html
I tried the #OrderColumn technique described in the above referenced stackoverflow article but this did not work in EclipseLink.
The solution that work for me was to add the EclipseLink nonstandard #CascadeOnDelete annotation to my entity as shown below:
#Entity
public class Company {
#NotNull
#ElementCollection
#CascadeOnDelete
private List<EntityEvent> history;
...
}
After performing this change and rebuilding my database, my COMPANY_HISTORY table has a new definition:
Without #CascadeOnDelete
ALTER TABLE COMPANY_HISTORY ADD CONSTRAINT CMPNYHISTORYCMPNYD FOREIGN KEY (COMPANY_ID) REFERENCES COMPANY (ID);
With #CascadeOnDelete
ALTER TABLE COMPANY_HISTORY ADD CONSTRAINT CMPNYHISTORYCMPNYD FOREIGN KEY (COMPANY_ID) REFERENCES COMPANY (ID) ON DELETE CASCADE;
The solution to my problem surprised me because it seems repetitive. My understanding is that JPA should delete all embeddables associated with an entity when the entity is deleted. The fact that EclipseLink has this nonstandard annotation as documented in the following link makes me think that EclipseLink has a bug and instead of fixing the bug created a new #CascadeOnDelete annotation so that the bug would be covered up by the databases cascading delete functionality.
http://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
So my question is why. Why does EclipseLink support the #CascadeOnDelete with #ElementCollection?
CascadeOnDelete is simply a feature that specifies that you have specified the "On Delete Cascade" option in your tables, so that JPA does not need to issue SQL to delete the corresponding references. This SQL can apply to any reference, which is why CascadeOnDelete works on an element collection mapping and any other referene mapping.
Your issue has to do with lob comparison limitation in your database, and since there isn't an ID field to uniquely identify element collection rows, this limitation interferes with the way EclipseLink tries to ensure it is only removing the required rows. If you were willing to add an order column to your table, why not just make the EntityEvent an Entity? Or you can customize EclipseLink as described here so that it uses the foreign key and an orderBy field or any combination of fields as a primary key to uniquely identify rows instead of including the lob field.

JPA create object by id

I have a following JPA entity:
#Entity
#Table(name = "sessions")
public class Session extends BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToOne
#JoinColumn(name = "user_id")
private User user;
...
}
In order to insert new Session object into database I need to perform a following steps:
Select user from db
Create new Session object, set user to this session object and then invoke sessionRepository.save(session)
Is it possible to avoid step #1 in case I know user_id ? I don't want to make redundant select to my database when possible.
Use EntityManager.getReference() instead of EntityManager.find() (in Spring-data-jpa crud repositories, this operation is named getOne()).
This returns a lazy, uninitialized User proxy, without executing any SQL query. Of course, you'd better have a foreign key constraint in the database to make sure the insert fails if you try to insert a new Session for an unexisting user.
There is an API that you can use for this.
If you use JPA, you can use entityManager.getReference() for loading user. This will not make a call to the database, unless you do something with the resulting instance.
A Hibernate equivalent for this is session.load(), which behaves in the same way.

Saving an entity without fetching ID

I have a table T1(ID AUTOINCREMENT INT, COL1, COL2, etc).
The JPA Entity for T1 has a field ID which has been marked as #Generated and #Id. This is to tell JPA that the key of the entity is not set by the application and that the DB will be generating the ID.
The code invokes the persist method to save the entity. I do not need the entity to be managed.
#Entity
#Table(name = "T1")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Long customerd;
#Column(name = "COL1")
private String col1;
When I run the application, JPA seems to INSERT the row in the table and immediately SELECT the generated ID to be set in the object.
Is it possible to tell JPA not to fetch the generated ID? I want JPA to perform the INSERT and forget the record.
The database is DB2. The JPA implementation is Hibernate.
Regards,
Yash
Per JPA Spec, every entity must have a primary key - can be simple or composite primary key. That means you have to specify at least a property to indicate it is the primary key of your entity by using #Id or #EmbeddedId annotations. JPA provider will ensure that the entity's generated id will be available in the Id annotated property during the persist call. The JPA provider will assign the Id either before or after the transaction has committed (depends of the generation strategy used). Regardless of strategy used and unless you manually set the Id property yourself, that won't prevent the JPA provider from querying the generated Id and assigning it to your Entity.

How to do many-to-many relation between the same entity

I have an Employee entity class with (Id,Name,EmployeeType). EmployeeType entity (Id, Description) where Description can be either REGULAR/MANAGER.
I am confused on how to map Employees who are of type REGULAR to their corresponding MANAGER type Employees. Should I just add an extra field to the Employee entity itself so that it now becomes (Id, Name, EmployeeType, ManagerEmployeeId)? Or should I instead have a lookup table Employee_Manager (Id, RegularEmployeeId, ManagerEmployeeId)?
I am considering going with the Employee_Manager lookup table and am not sure how that entity class would look like. The following below is what comes to my mind. Am I on the right track here?
#Entity
#Table(name="EMPLOYEE")
public class Employee{
#Id
int id;
#Column(name="NAME")
String name;
#ManyToMany(mappedBy = "regularEmployee")
Collection<Employee> regularEmployee
#ManyToMany
Collection<Employee> managerEmployee;
}
ps. I am using JPA with Hibernate as the persistence provider.
If you're trying to have an employee have exactly one manager, then first of all you're doing a many-to-one relation (not many-to-many) and having the ManagerEmployeeID in the table as a foreign key reference to the same table is just fine.
Use a lookup table if you want to allow an employee to potentially have more than one managerial-type role. You can also use this if you want to assign a particular "role" to these manager-type people:
create table Supervisors (
eid int,
sid int,
role varchar(16)
);
Then you could use role for "Supervisor" vs "Manager" vs "Slavedriver" vs who knows what.
Sorry, I don't know any JPA/Hibernate, so the concepts (and pseudo-SQL) is the best I can give you.
Hope that helps a bit.