We’ve already discussed a different kind of primary keys, either simple or composite primary keys have assigned an inline values in the executable applications that were made. by looking into @Id, @IdClass or @EmbeddedId examples you could see that. But what if we would have some generator to generate these primary values. This tutorial explains the ID generator concept in details. The Java Persistence API including the EclipseLink distribution provides several types of primary key generation annotations.
@GeneratedValue
The @GeneratedValue consider the entry point for primary key generation, it provides the specification of generation strategies for the values of primary keys. The GeneratedValue annotation may be applied to a primary key property of field of an entity or mapped superclass in a conjunction with the Id annotation. The values that can be used with the @GeneratedValue are those values defined inside the enum GenerationType. GenerationType.java
public enum GenerationType {TABLE,SEQUENCE,IDENTITY,AUTO};
- TABLE: Is a generator type value indicates that the must assign primary keys for the entity using a Database Table to ensure uniqueness.
- SEQUENCE & IDENTITY: Are a generator types that specify the use of a database sequence or identity column respectively.
- AUTO: Is a generator type indicates that the persistence provider should select an appropriate strategy for the particular database.
Anatomy of @GeneratedValue
- Target: Field and Method
- Uses:@GeneratedValue
- Argument:
- strategy(Optional): The primary key generation strategy that the persistence provider must use to generate the annotated entity primary key.
- generator (Optional): The name of the primary generator to use as specified in the SequenceGenerator or TableGenerator.
Let’s see the implementation of different strategies using the same entity. As you’ve noted before in the EclipseLink Tutorial, we’ve created a mapped superclass previously, so this tutorial should clarify all strategies using License class example.
License Entity
The following class shows you the License entity without using the @GeneratedValue.
package net.javabeat.eclipselink.data; import javax.persistence.CascadeType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; @MappedSuperclass public abstract class License { @Id protected int licenseId; // This primary key field has no generator @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "employeeId") private Employee employee; public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } public int getLicenseId() { return licenseId; } public void setLicenseId(int licenseId) { this.licenseId = licenseId; } }
- The License mapped superclass provide an identity named licenseId.
- The licenseId has no primary key generator
- In case we’ve not used a generator and not provided a primary key value, the JPA will consider the default value for int primitive types (the default value is zero) and the persist will success for the first time. But when it comes into the second persist the JPA will throw an exception indicate that (Duplicate entry ‘0’ for key primary key).
Primary Key Generation Using Sequence Generator
At this example, you would be seeing a database sequence for generating a primary keys.
Anatomy of @SequenceGenerator
The @SequenceGenerator annotation defines a primary key generator that may be referenced by name when a generator element is specified for the GeneratedValue annotation.A sequence generator may be specified on the entity class or on the primary key field or property.
- Target: Type, Method and Field
- Uses:@SequenceGenerator
- Argument:
- name (Required): A unique generator name that can be referenced by one or more classes to be the generator for primary key values.
- sequenceName (Optional): The name of the database sequence object from which to obtain primary key values.
- initialValue (Optional): The value from which the sequence object is to start generating.
- allocationSize (Optional): The amount to increment by when allocating sequence numbers from the sequence.
Database Sequence Creation
This sample of sequence creation should use the Oracle database, cause MySQL support auto increment which is not consider as a sequence. Figure 1.0 and 1.1 shows you the DriverLicense Table and sequence that created on the Oracle database. Note that the DriverLicense Table doesn’t consider any further relations as you’ve seen in the previous examples.
Figure 1.0
Figure 1.1
License Entity Uses a Sequence Generator
The following License entity does use the sequence generator for generating the primary keys for the entities that created from the License (in case the license isn’t mapped superclass) and from any entity belongs to the License inheritance. License.java
package net.javabeat.eclipselink.data; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import javax.persistence.SequenceGenerator; @MappedSuperclass public abstract class License { @Id @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="LICENSE_SEQ") @SequenceGenerator(name="LICENSE_SEQ",sequenceName="LICENSE_SEQ",allocationSize=1) protected int licenseId; public int getLicenseId() { return licenseId; } public void setLicenseId(int licenseId) { this.licenseId = licenseId; } }
DriverLicense.java
package net.javabeat.eclipselink.data; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity @Table(name="driverlicenses") public class DriverLicense extends License{ private String driverLicenseName; @Temporal(TemporalType.DATE) private Date driverLicenseExpiryDate; @Temporal(TemporalType.DATE) private Date driverLicenseIssueDate; public String getDriverLicenseName() { return driverLicenseName; } public void setDriverLicenseName(String driverLicenseName) { this.driverLicenseName = driverLicenseName; } public Date getDriverLicenseExpiryDate() { return driverLicenseExpiryDate; } public void setDriverLicenseExpiryDate(Date driverLicenseExpiryDate) { this.driverLicenseExpiryDate = driverLicenseExpiryDate; } public Date getDriverLicenseIssueDate() { return driverLicenseIssueDate; } public void setDriverLicenseIssueDate(Date driverLicenseIssueDate) { this.driverLicenseIssueDate = driverLicenseIssueDate; } }
- The License entity uses the @GeneratedValue for providing a both of generator type and name.
- The generator type is SEQUENCE and the name is LICENSE_SEQ.
- The License entity uses the @SequenceGenerator for providing the name of the sequence that’s would be consumed by the @GeneratedValue, the name of the database sequence and the allocation size.
Required Persistence Configuration
persistence.xml
<persistence-unit name="EclipseLink-JPA-OracleDS" transaction-type="RESOURCE_LOCAL"> <!-- Entities Created Before --> <class>net.javabeat.eclipselink.data.License</class> <class>net.javabeat.eclipselink.data.DriverLicense</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:xe"/> <property name="javax.persistence.jdbc.user" value="ucm"/> <property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/> <property name="javax.persistence.jdbc.password" value="ucm"/> </properties> </persistence-unit>
Required Libraries
As you’ve noted in the persistence.xml above, the database that’s being used for achieving a sequence generator is an Oracle database, so that you’ve had installed a new library in your project classpath that’s JAR is called ojdbc14.jar. for installing a new library you have to follow the below steps:
- Right click on the project that you would be adding the JAR in its classpath.
- Select Java Build Path from the left pane.
- From the Java Build Path Area click on Add External JARs.
- Add the JAR by navigating into its JAR location.
Executable Application for Generating a Primary keys using Sequence Generator
JPAImpl.java
package net.javabeat.eclipselink; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import net.javabeat.eclipselink.data.Address; import net.javabeat.eclipselink.data.AddressPK; import net.javabeat.eclipselink.data.ContractDeveloper; import net.javabeat.eclipselink.data.Developer; import net.javabeat.eclipselink.data.DriverLicense; import net.javabeat.eclipselink.data.Employee; import net.javabeat.eclipselink.data.EmployeePeriod; import net.javabeat.eclipselink.data.FreelanceDeveloper; import net.javabeat.eclipselink.data.GlobalProject; import net.javabeat.eclipselink.data.ICDLComputerLicense; import net.javabeat.eclipselink.data.LocalProject; import net.javabeat.eclipselink.data.Phone; import net.javabeat.eclipselink.data.PhonePK; import net.javabeat.eclipselink.data.Project; public class JPAImpl { static EntityManagerFactory factory = null; static EntityManager em = null; static { factory = Persistence.createEntityManagerFactory("EclipseLink-JPA-OracleDS"); em = factory.createEntityManager(); } public static void main(String [] args){ // Begin a Transaction em.getTransaction().begin(); // Create a Driver License createDriverLicense(); // Commit em.getTransaction().commit(); } public static void createDriverLicense(){ DriverLicense license = new DriverLicense(); // Create a driver license license.setDriverLicenseName("All Vehicles License"); // Set License Name license.setDriverLicenseIssueDate(new Date()); // Anonymous date license.setDriverLicenseExpiryDate(new Date()); // Anonymous date em.persist(license); } }
- The DriverLicense entity doesn’t provide a primary key as you’ve noted in the provided examples at the EclipseTutorial.
- The primary keys are generated automatically using a SequenceGenetator that defined in the License mapped superclass.
The Persisted Records Using Sequence Generator
Figure 1.2 shows you the records that persisted into the Oracle database using a sequence generator.
Figure 1.2
Primary Key Generation Using Table Generator
At this example, you would be seeing a database table for generating a primary keys.
Anatomy of @TableGenerator
The @TableGenerator annotation defines a primary key generator that may be referenced by name when a generator element is specified for the @GeneratedValue. A Table generator may be specified on the entity class or on the primary key field or property.
- Target: Type, Method and Field
- Uses:@TableGenerator
- Argument:
- name (Required): A unique generator name that can be referenced by one or more classes to be the generator for id values.
- table (Optional): Name of table that stores the generated for id values.
- catalog (Optional): The catalog of the table.
- schema (Optional): The schema of the table.
- pKColumnName (Optional): Name of the primary key column in the table.
- valueColumnName (Optional): Name of the column that stores the last value generated.
- pKColumnValue (Optional): The primary key value in the generator table that distinguishes this set of generated values from others that may be stored in the table.
- initialValue (Optional): The value used to initialize the column that stores the last value generated.
- allocationSize (Optional): The amount to increment by when allocating id numbers from the generator.
- uniqueConstraints (Optional): Unique constraints that are to be placed on the table.
Database Table Sequence Creation
MySQL database is capable to create table sequence, so at this section of primary key generation we would return back into the previous examples that already made using the MySQL database. Figure 1.0 and 1.1 shows you the Table sequence and DriverLicense Table that created on the MySQL database respectively.
Figure 1.0
Figure 1.1
- The Table sequence that created contains two columns one for sequence name and the other for sequence number.
- Nothing changed on drive licenses table.
License Entity Uses a Table Sequence Generator
The following License entity does use the Table sequence generator for generating the primary keys for the entities that created from the License (in case the license isn’t mapped superclass) and from any entity belongs to the License inheritance. License.java
package net.javabeat.eclipselink.data; import javax.persistence.CascadeType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; import javax.persistence.TableGenerator; @MappedSuperclass public abstract class License { @Id @GeneratedValue(generator="LICENSE_TABLE_SEQ",strategy=GenerationType.TABLE) @TableGenerator(name="LICENSE_TABLE_SEQ", table="sequences", pkColumnName="SEQ_NAME", // Specify the name of the column of the primary key valueColumnName="SEQ_NUMBER", // Specify the name of the column that stores the last value generated pkColumnValue="LICENSE_ID", // Specify the primary key column value that would be considered as a primary key generator allocationSize=1) protected int licenseId; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "employeeId") private Employee employee; public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } public int getLicenseId() { return licenseId; } public void setLicenseId(int licenseId) { this.licenseId = licenseId; } }
DriverLicense.java
package net.javabeat.eclipselink.data; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity @Table(name="driverlicenses") public class DriverLicense extends License{ private String driverLicenseName; @Temporal(TemporalType.DATE) private Date driverLicenseExpiryDate; @Temporal(TemporalType.DATE) private Date driverLicenseIssueDate; public String getDriverLicenseName() { return driverLicenseName; } public void setDriverLicenseName(String driverLicenseName) { this.driverLicenseName = driverLicenseName; } public Date getDriverLicenseExpiryDate() { return driverLicenseExpiryDate; } public void setDriverLicenseExpiryDate(Date driverLicenseExpiryDate) { this.driverLicenseExpiryDate = driverLicenseExpiryDate; } public Date getDriverLicenseIssueDate() { return driverLicenseIssueDate; } public void setDriverLicenseIssueDate(Date driverLicenseIssueDate) { this.driverLicenseIssueDate = driverLicenseIssueDate; } }
Required Persistence Configuration
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="EclipseLink-JPA-Installation" transaction-type="RESOURCE_LOCAL"> <!-- Some Entities have omit intentionally --> <class>net.javabeat.eclipselink.data.License</class> <class>net.javabeat.eclipselink.data.DriverLicense</class> <class>net.javabeat.eclipselink.data.Employee</class> <class>net.javabeat.eclipselink.data.ICDLComputerLicense</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/JavaBeat"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="javax.persistence.jdbc.password" value="root"/> <property name="eclipselink.logging.level" value="FINEST"/> <property name="" value=""/> </properties> </persistence-unit>
- If you’ve never ever see the previous entities before and you would learn more about it, refer to the EclipseLink Tutorial.
- The DriverLicense is mentioned in the persistence.xml
- The License mapped superclass is mentioned in the persistence.xml
- No need to mention the generator of the primary keys.
Executable Application for Generating a Primary keys using Table Sequence Generator
JPAImpl.java
package net.javabeat.eclipselink; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import net.javabeat.eclipselink.data.Address; import net.javabeat.eclipselink.data.AddressPK; import net.javabeat.eclipselink.data.ContractDeveloper; import net.javabeat.eclipselink.data.Developer; import net.javabeat.eclipselink.data.DriverLicense; import net.javabeat.eclipselink.data.Employee; import net.javabeat.eclipselink.data.EmployeePeriod; import net.javabeat.eclipselink.data.FreelanceDeveloper; import net.javabeat.eclipselink.data.GlobalProject; import net.javabeat.eclipselink.data.ICDLComputerLicense; import net.javabeat.eclipselink.data.LocalProject; import net.javabeat.eclipselink.data.Phone; import net.javabeat.eclipselink.data.PhonePK; import net.javabeat.eclipselink.data.Project; public class JPAImpl { static EntityManagerFactory factory = null; static EntityManager em = null; static { factory = Persistence.createEntityManagerFactory("EclipseLink-JPA-Installation"); em = factory.createEntityManager(); } public static void main(String [] args){ // Begin a Transaction em.getTransaction().begin(); // Find the Employee Employee employee = em.find(Employee.class, 1); // Create a Driver License createDriverLicense(employee); // Commit em.getTransaction().commit(); } public static void createDriverLicense(Employee employee){ DriverLicense license = new DriverLicense(); // Create a driver license license.setDriverLicenseName("All Vehicles License"); // Set License Name license.setDriverLicenseIssueDate(new Date()); // Anonymous date license.setDriverLicenseExpiryDate(new Date()); // Anonymous date license.setEmployee(employee); em.persist(license); } }
The Persisted Records Using Table Sequence Generator
Figure 1.2 shows you the records that persisted into the Oracle database using a Table sequence generator.
Figure 1.2
The impact of adding three records of driver license on the Table sequence is shown at the Figure 1.3
Figure 1.3
- If you’ve noted about the SEQ_NUMBER, it’s value is 3 cause this is the last number that has been generated.
IDENTITY & AUTO Generators
The IDENTITY value specify the use of an identify column, so if you’ve noted the License mapped superclass, you should be able see the licenseId as an identified property. The IDENTITY strategy is one of the most simple strategy that could be applied. Let’s look at the License entity if we’ve decided to use the IDENTITY strategy. License.java
package net.javabeat.eclipselink.data; import javax.persistence.CascadeType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; @MappedSuperclass public abstract class License { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) protected int licenseId; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "employeeId") private Employee employee; public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } public int getLicenseId() { return licenseId; } public void setLicenseId(int licenseId) { this.licenseId = licenseId; } }
- The primary key of the License entity should be annotated using @GeneratedValue, but this time with an IDENTITY as generator.
Also, your database engine should support the AUTO_INCREMENT principle. Figure 1.4 shows you the updated licenseId column.
Figure 1.4
- The licenseId primary key is updated to be Auto Increment.
That’s what you have to do if you’ve selected and Identity as a primary key generator. Now let’s execute the previous executable JPAImpl. The JPAImpl will execute smoothly without change, that’s because the IDENTITY needs no more that an auto incremental primary key column. See Figure 1.5 that shows you an additional records that persisted using IDENTITY.
Figure 1.5
AUTO value indicates that the persistence provider should pick an appropriate strategy for the particular database, in case we’ve used a MySQL and annotate the License entity using Auto, we almost probably getting such that exception.
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'javabeat.sequence' doesn't exist at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at com.mysql.jdbc.Util.handleNewInstance(Util.java:411) at com.mysql.jdbc.Util.getInstance(Util.java:386) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1054) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4120) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4052) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2503) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2664) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2815) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2375) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2359) at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:890) ... 32 more
It’s clear that the JPA select sequence as a primary key strategy for generating a primary keys. In general Auto generation may expect a database resource to exist, or it may attempt to create one. A vendor may provide dcoumention on how to create such resources in the event that it doesn’t support schema generation or cannot create schema resource at runtime.
Summary
This tutorial is intended to explain the different ways of generating a primary keys. We’ve already clarified the using of @GeneratedValue annotation and the different possible values that could occurred within it. The JPA provides a lot of generator that ranging from simple to complex. The most simple generators that could be used are IDENTITY and AUTO, meanwhile the most complex generators that are used a little more work to achieve are SEQUENCE and TABLE SEQUENCE.