JPA - 1
Prelude
JPA (Java Persistence API ) itself is NOT a tool or a framework; it is a definition of concepts that can be implemented by any tool or framework. I think being able to use JPA implementation is quintessential for developers nowadays. Without using JPA, when I had to add one new query for business logic, I had to add two SQLs for separate databases(postgreSQL and MSSQL), and method to mapper.java, dao, service interface and its implemented class. It was a sheer waste of energy and time, and moreover, even with it there still were elements of errors by typo or miscalcualted logic.
In studying JPA, I figure understanding the below two concepts is very important.
- Mapping object to RDBMS (N:1, 1:N, M:M, 1:1)
- PersistenceContenxt
1. Starting with a simple example
- Starting off, you need create a maven project (or any other project management tool) and add the following dependencies.
<dependencies>
<!-- JPA hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.12.Final</version>
</dependency>
<!-- H2 database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
</dependencies>
- For testing purpose, I will use H2 database. You can download it easily from its website.
- Add persistence.xml to resources/META-INF/. This is what Maven specifies the location of .xml files to be in order to be read and processed.
<persistence version="2.2"
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_2.xsd">
<persistence-unit name="jpaMain1">
<properties>
<!-- necessary properties -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test3"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- optional -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
persistence-unit name will be the name of the EntityManagerFactory. “javax.persistence.jdbc.url” is the name of the database. Optional values are for help developers understand better what is happening with the queries. These will be dealt in details.
- After setting up the basic environment as such, let’s go to the actual implementation.
@Entity
public class Account {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
private String color;
}
- Adding @Entity annotation is the first step to mapping Java Objects to Persisting DB data.
- @Id @GeneratedValue sets the unique primary key value for each of the rows to be saved in the database.
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaMain1");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try{
}catch(Exception e){
transaction.rollback();
}finally{
em.close();
}
}
- Above code is the plain code where Spring or Springboot does not intervene. It is recommended that
- EntityManager must not be shared among different threads.
-
There should be only one EntityManagerFactory for one application.
- After executing the above main method, you can find the executed queries from the console, and tables created in your H2 database
- [Important] It seems that some words are reserved in SQL. I originally had Group to be a classname, and error popped out while executing above main code. Make sure to read error messages and choose appropriate name when naming classes.
2. Entity Mapping
2-1. Mapping annotations
- Below is a more specified blueprint of Account object.
@Entity
public class Account {
@Id @GeneratedValue
private Long id;
@Column(name="name")
private String accountName;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
@Lob
private byte[] cover;
}
Annotation | Explanation |
---|---|
@Column | Mapping column |
@Temporal | Mapping time and date |
@Enumerated | Mapping enumeration |
@Lob | Mapping BLOB CLOB |
@Transient | Excluding the field from being mapped |
- When using @Enumerated, it is 100% recommended to use EnumType.String instead of EnumType.Ordinal. Using Ordinal can be disastrous if you have to add addtional types or change the order of existing enum values, since as name suggests EnumType.Ordinal marks enum values with numbers.
2-2. Optinal values in persistence.xml
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
- The above optional values are self-explanatory, perhaps except for hibernate.hbm2ddl.auto.
Option | Explanation |
---|---|
create | Drop existing tables and create new |
create-drop | Drop tables upon exiting (same with create, except for time of dropping) |
update | Apply changes to DB (shouldn’t be used in real business) |
validate | Check if entities and tables are mapped correctly |
none | not option applied |
- It is recommended not to use update for real business situations because the impact of updated schema may not have been thoroughly examined, causing unexpected errors.
3. Object Relations - About Owning side of a relationship
- It is important to know that JPA came out in an attempt to manipulate data in database like collections do. For instance, in using collection APIs, this kind of operation is very normal and natural.
List<Account> accounts = new ArrayList<>(); //suppose some data is already stored.
Account account1 = new Account("Mina");
accounts.add(account1); // new account data is stored in 'accounts'.
- To be able to do such stuffs, you have to set relations between objects.
3-1. Understanding difference between object mapping and table mapping
-
For a database system, establishing a relation between two tables can be done easily using primary key and foreign key.
- Adding the primary key of one table as a foreign key, a query like
SELECT * FROM ACCOUNT A JOIN TEAM T ON A.TEAM_ID = T.TEAM_ID;
is possible.
- For Object mapping, you have to annotate the columns of interest so that JPA can understand their relations.
@Entity
public class Account {
@Id @GeneratedValue
@Column(name="ACCOUNT_ID")
private Long id;
...
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
private String color;
}
- @ManyToOne suggests that Many (accounts) are linked To One(Team), which makes a good sense.
- Executing the main method after modifying the class specification,
Hibernate: create table Account ( ACCOUNT_ID bigint not null, name varchar(255), cover blob, createDate timestamp, roleType varchar(255), TEAM_ID bigint, primary key (ACCOUNT_ID) ) Hibernate: create table Team ( TEAM_ID bigint not null, color varchar(255), name varchar(255), primary key (TEAM_ID) ) Hibernate: alter table Account add constraint FKq968tvkxqb8pbbpqg465jnn0r foreign key (TEAM_ID) references Team
- You can note that the foreign key constraint was imposed on Acount table.
- There can be total of four relations between two tables. N:1, 1:N, 1:1, N:M. In the next posting, I will cover those four relations and give examples on how one should implement those and what one needs to be careful about.