Java Persistence API(JPA)是将Java对象和关系型数据库对象映射起来规范。实现这个规范后开发者可以使用相同的代码可以在任意的数据库中执行CRUD操作,实现的框架不仅仅是处理和数据库交换的代码(JDBC),同时也会将数据库中的数据和Java对象映射起来,无需手动进行转换。此教程基于JAP2.1。 JPA 主要包含的组件:
首先创建项目:
mvn archetype:create -DgroupId=com.javacodegeeks.ultimate -DartifactId=jpa
添加依赖:
<properties>
<jee.version>7.0</jee.version>
<h2.version>1.3.176</h2.version>
<hibernate.version>4.3.8.Final</hibernate.version>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>${jee.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
配置 persistence.xml信息,文件保存在src/main/resource/META-INF下。
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="PersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="connection.driver_class" value="org.h2.Driver"/>
<property name="hibernate.connection.url" value="jdbc:h2:~/jpa"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
事务类型设置的是RESOURCE_LOCAL,表示事务由应用自己控制,如果使用了容器提供的事务可以设置为JTA。 provider 设置为org.hibernate.ejb.HibernatePersistence 表示使用Hibernate实现的JPA。 之后的设置就是设置JPA连接数据库的基本信息。
public class Main {
private static final Logger LOGGER = Logger.getLogger("JPA");
public static void main(String[] args) {
Main main = new Main();
main.run();
}
public void run() {
EntityManagerFactory factory = null;
EntityManager entityManager = null;
try {
factory = Persistence.createEntityManagerFactory("PersistenceUnit");
entityManager = factory.createEntityManager();
persistPerson(entityManager);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
e.printStackTrace();
} finally {
if (entityManager != null) {
entityManager.close();
}
if (factory != null) {
factory.close();
}
}
}
...
JPA中所有的操作基本都由EntityManager完成。为了获取EntityManger,需要创建EntityManagerFactory实例。一个应用尽需要一个EntityManagerFactory。
现在来实现上面代码的persistPersion()方法,以为我们选择的是事务类型是本地事务,所有事务要有应用控制,存储一个对象
private void persistPerson(EntityManager entityManager) {
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
Person person = new Person();
person.setFirstName("Homer");
person.setLastName("Simpson");
entityManager.persist(person);
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
上述的Person对象的定义:表名为T_PERSION
@Entity
@Table(name = "T_PERSON")
public class Person {
private Long id;
private String firstName;
private String lastName;
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "FIRST_NAME")
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name = "LAST_NAME")
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
如果表明和类名相同,那么@Table是可以省略的,但是仍然推荐加上。 注解@Column 是用来映射Java对象和表中的列的,及时不加注解,JPA仍然会映射,除非其使用注解@Transient修饰,则不会被映射。关于@Column的使用
@Colunm(name="FIRST_NAME", length=100, nullable = false, unique = false)
上述注解的意思就是映射表中列名为FIRST_NAME的列,长度100字符,不能空,不唯一,当试图插入null值是会抛出异常并会滚事务。 对于@Id和@GeneratedValue是告诉JAP,这个值是主键并且其值是由数据库自动生成的。 上述例子,@Column是修饰getter的同样可以直接修饰字段。
@Entity
@Table(name = "T_PERSON")
public class Person {
@Id
@GeneratedValue
private Long id;
@Column(name = "FIRST_NAME")
private String firstName;
@Column(name = "LAST_NAME")
private String lastName;
...
这两种方法几乎没什么区别,唯一的区别在于如果注解修饰字段子类无法重写其注解。 另一个需要注意的是需要在一个实体的层次上使用一种注解方式。可以在JPA的整个项目混用注解字段或者方法,但是在一个实体和它的子类中需要确保使用的是同一种注解方式。如果要修改子类的注解方式,可以使用 @Access注解改变
@Entity
@Table(name = "T_GEEK")
@Access(AccessType.PROPERTY)
public class Geek extends Person {
...
对于Geek类,其直接继承与Person
@Entity
@Table(name = "T_GEEK")
public class Geek extends Person {
private String favouriteProgrammingLanguage;
private List<Project> projects = new ArrayList<Project>();
@Column(name = "FAV_PROG_LANG")
public String getFavouriteProgrammingLanguage() {
return favouriteProgrammingLanguage;
}
public void setFavouriteProgrammingLanguage(String favouriteProgrammingLanguage) {
this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;
}
...
}
Hibernate 会重新创建T_PERSON表
Hibernate: create table T_PERSON (DTYPE varchar(31) not null, id bigint generated by default as identity, FIRST_NAME varchar(255), LAST_NAME varchar(255), FAV_PROG_LANG varchar(255), primary key (id))
保存Geek
private void persistGeek(EntityManager entityManager) {
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
Geek geek = new Geek();
geek.setFirstName("Gavin");
geek.setLastName("Coffee");
geek.setFavouriteProgrammingLanguage("Java");
entityManager.persist(geek);
geek = new Geek();
geek.setFirstName("Thomas");
geek.setLastName("Micro");
geek.setFavouriteProgrammingLanguage("C#");
entityManager.persist(geek);
geek = new Geek();
geek.setFirstName("Christian");
geek.setLastName("Cup");
geek.setFavouriteProgrammingLanguage("Java");
entityManager.persist(geek);
transaction.commit();
}
T_PERSION保存的数据
sql> select * from t_person;
DTYPE | ID | FIRST_NAME | LAST_NAME | FAV_PROG_LANG
Person | 1 | Homer | Simpson | null
Geek | 2 | Gavin | Coffee | Java
Geek | 3 | Thomas | Micro | C#
Geek | 4 | Christian | Cup | Java
DTYPE表示了不同类型的PERSON,对于Person,其FAV_PROG_LANG是null。 如果不喜欢使用字符串类型的鉴别器,可以设置@DiscriminatorColumn来表示不同的类型
@DiscriminatroColumn(name="PERSION_TYPE",discriminatorType=DiscriminatroType.INTEGER)
存储结果:
sql> select * from t_person;
PERSON_TYPE | ID | FIRST_NAME | LAST_NAME | FAV_PROG_LANG
-1907849355 | 1 | Homer | Simpson | null
2215460 | 2 | Gavin | Coffee | Java
2215460 | 3 | Thomas | Micro | C#
2215460 | 4 | Christian | Cup | Java
并不是所有情况下都想把信息存储在一张表中,可以使用@Inheritance选择不同的存储策略,对于这种一共有三种选择:
create table T_PERSON ( id bigint generated by default as identity, FIRST_NAME varchar(255), LAST_NAME varchar(255), ID_CARD_ID bigint, primary key (id) )
@OneToOne有一个重要特性就是可以设置何时获取IdCard。
```Java
@OneToOne(fetch = FetchType.EAGER)
默认值EAGER表示每次获取Person都要获取IdCard
@OneToOne(fetch = FetchType.LAZY)
表示只有在需要IdCard的时候才会去获取 其获取SQL如下:
Hibernate:
select
person0_.id as id1_3_,
person0_.FIRST_NAME as FIRST_NA2_3_,
person0_.ID_CARD_ID as ID_CARD_4_3_,
person0_.LAST_NAME as LAST_NAM3_3_,
person0_1_.FAV_PROG_LANG as FAV_PROG1_1_,
case
when person0_1_.id is not null then 1
when person0_.id is not null then 0
end as clazz_
from
T_PERSON person0_
left outer join
T_GEEK person0_1_
on person0_.id=person0_1_.id
Hibernate:
select
idcard0_.id as id1_2_0_,
idcard0_.ID_NUMBER as ID_NUMBE2_2_0_,
idcard0_.ISSUE_DATE as ISSUE_DA3_2_0_
from
T_ID_CARD idcard0_
where
idcard0_.id=?
@Entity
@Table(name = "T_PHONE")
public class Phone {
private Long id;
private String number;
private Person person;
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "NUMBER")
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PERSON_ID")
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
对于Person
private List<Phone> phones = new ArrayList<>();
...
@OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
public List<Phone> getPhones() {
return phones;
}
@OneToMany 的mappedBy 表示使用Phone中的person字段来关联。其fetch设置为LAZY模式,因为每次获取Person的时候不一定需要其Phone,如果需要可以通过如下方式获取:
TypedQuery<Person> query = entityManager.createQuery("from Person p left join fetch p.phones", Person.class);
对应的SQL如下:
select
person0_.id as id1_3_0_,
phones1_.id as id1_4_1_,
person0_.FIRST_NAME as FIRST_NA2_3_0_,
person0_.ID_CARD_ID as ID_CARD_4_3_0_,
person0_.LAST_NAME as LAST_NAM3_3_0_,
person0_1_.FAV_PROG_LANG as FAV_PROG1_1_0_,
case
when person0_1_.id is not null then 1
when person0_.id is not null then 0
end as clazz_0_,
phones1_.NUMBER as NUMBER2_4_1_,
phones1_.PERSON_ID as PERSON_I3_4_1_,
phones1_.PERSON_ID as PERSON_I3_3_0__,
phones1_.id as id1_4_0__
from
T_PERSON person0_
left outer join
T_GEEK person0_1_
on person0_.id=person0_1_.id
left outer join
T_PHONE phones1_
on person0_.id=phones1_.PERSON_ID
@Entity
@Table(name = "T_PROJECT")
public class Project {
private Long id;
private String title;
private List<Geek> geeks = new ArrayList<Geek>();
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "TITLE")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToMany(mappedBy="projects")
public List<Geek> getGeeks() {
return geeks;
}
public void setGeeks(List<Geek> geeks) {
this.geeks = geeks;
}
}
通过@ManyToMany中的mappedBy得知,需要在Geek中的字段projects做多对多关系 Geek类:
private List<Project> projects = new ArrayList<>();
...
@ManyToMany
@JoinTable(
name="T_GEEK_PROJECT",
joinColumns={@JoinColumn(name="GEEK_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="PROJECT_ID", referencedColumnName="ID")})
public List<Project> getProjects() {
return projects;
}
在Geek类中提供了@JoinTable明确其和哪个表做join,joinColunms和inverseJoinConlumns则表示如何做JOIN。 如果是在Project中实现是同样的,只需要将joinColumns和inverseJoinColumn换一下即可 Project类
@ManyToMany
@JoinTable(
name="T_GEEK_PROJECT",
joinColumns={@JoinColumn(name="PROJECT_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="GEEK_ID", referencedColumnName="ID")})
public List<Geek> getGeeks() {
return geeks;
}
Geek类:
@ManyToMany(mappedBy="geeks")
public List<Project> getProjects() {
return projects;
}
获取如下:
List<Geek> resultList = entityManager.createQuery("from Geek g where g.favouriteProgrammingLanguage = :fpl", Geek.class).setParameter("fpl", "Java").getResultList();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
Project project = new Project();
project.setTitle("Java Project");
for (Geek geek : resultList) {
project.getGeeks().add(geek);
geek.getProjects().add(project);
}
entityManager.persist(project);
transaction.commit();
如果想要在Java中更加细粒度的控制其model可以使用嵌入模式。例如Project有startDate 和 endDate,可以创建Period类以便重用开始和结束时间。
@Embeddable
public class Period {
private Date startDate;
private Date endDate;
@Column(name ="START_DATE")
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
@Column(name ="END_DATE")
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}
在Project中加入:
private Period projectPeriod;
@Embedded
public Period getProjectPeriod() {
return projectPeriod;
}
public void setProjectPeriod(Period projectPeriod) {
this.projectPeriod = projectPeriod;
}
Project表结构:
create table T_PROJECT (
id bigint generated by default as identity,
END_DATE timestamp,
START_DATE timestamp,
projectType varchar(255),
TITLE varchar(255),
primary key (id)
)
查询:
entityManager.createQuery("from Project p where p.projectPeriod.startDate = :startDate", Project.class).setParameter("startDate", createDate(1, 1, 2015));
SQL类型 | java类型 |
---|---|
VARCHAR (CHAR, VARCHAR2, CLOB, TEXT) | String (char, char[]) |
NUMERIC (NUMBER, INT, LONG, FLOAT, DOUBLE) | Number (BigDecimal, BigInteger, Integer, Double, Long, Float, Short, Byte) |
NUMERIC (NUMBER, INT, LONG, FLOAT, DOUBLE) | int, long, float, double, short, byte |
VARBINARY (BINARY, BLOB) | byte[] |
BOOLEAN (BIT, SMALLINT, INT, NUMBER) | boolean(Boolean) |
TIMESTAMP (DATE, DATETIME) | java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar |
NUMERIC (VARCHAR, CHAR) | java.lang.Enum |
VARBINARY (BINARY, BLOB) | java.util.Serializable |
例如使用enum
@Entity
@Table(name = "T_PROJECT")
public class Project {
...
private ProjectType projectType;
public enum ProjectType {
FIXED, TIME_AND_MATERIAL
}
...
@Enumerated(EnumType.ORDINAL)
public ProjectType getProjectType() {
return projectType;
}
public void setProjectType(ProjectType projectType) {
this.projectType = projectType;
}
}
通过使用@Enumerated 将enum和数据库的字段进行映射,EnumType.ORDINAL 表示使用数字表示enum并保存到数据中。
sql> select * from t_project;
ID | PROJECTTYPE | TITLE
1 | 1 | Java Project
(1 row, 2 ms)
如果使用 EnumType.String 则表示使用String保存,调用enum的方法name()获取其名字。
sql> select * from t_project;
ID | PROJECTTYPE | TITLE
1 | TIME_AND_MATERIAL | Java Project
(1 row, 2 ms)
如果没有满足需要的转换器,可以自己构建
@Converter
public class BooleanConverter implements AttributeConverter<Boolean, Integer> {
@Override
public Integer convertToDatabaseColumn(Boolean aBoolean) {
if (Boolean.TRUE.equals(aBoolean)) {
return 1;
} else {
return -1;
}
}
@Override
public Boolean convertToEntityAttribute(Integer value) {
if (value == null) {
return Boolean.FALSE;
} else {
if (value == 1) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
}
}
使用
private boolean valid;
...
@Column(name = "VALID")
@Convert(converter = BooleanConverter.class)
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
结果如下:
sql> select * from t_id_card;
ID | ID_NUMBER | ISSUE_DATE | VALID
1 | 4711 | 2015-02-04 16:43:30.233 | -1
目前为止一直使用JPQL来查询数据库,现在通过使用JPQL提供的标准的API来查询。 例如:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> personRoot = query.from(Person.class);
query.where(builder.equal(personRoot.get("firstName"), "Homer"));
List<Person> resultList = entityManager.createQuery(query).getResultList();
首先通过EntityManger创建CriteriaBuilder。通过CruiteriaBuilder创建CriteriaQuery。提供一个函数获取where语句
query.where(builder.equal(personRoot.get("firstName"), "Homer"));
对于多where语句
query.where(builder.and(
builder.equal(personRoot.get("firstName"), "Homer"),
builder.equal(personRoot.get("lastName"), "Simpson")));
CriteriaQuery定义了一下子句和选项:
对于@GeneratedValue提供了三种策略:
例如使用TABLE
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "TABLE_GENERATOR")
@TableGenerator(name = "TABLE_GENERATOR", table="T_SEQUENCES", pkColumnName = "SEQ_NAME", valueColumnName = "SEQ_VALUE", pkColumnValue = "PHONE")
public Long getId() {
return id;
}
SQL
sql> select * from t_sequences;
SEQ_NAME | SEQ_VALUE
PHONE | 1
使用SEQUENCE
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "S_PROJECT")
@SequenceGenerator(name = "S_PROJECT", sequenceName = "S_PROJECT", allocationSize = 100)
public Long getId() {
return id;
}
通过@SequenceGenerator JPA获取其SEQUENCE,名字为S_PROJECT,分配的大小(100)。 使用IDENTITY
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
表:
create table T_ID_CARD (
id bigint generated by default as identity,
ID_NUMBER varchar(255),
ISSUE_DATE timestamp,
VALID integer,
primary key (id)
)