1 JPA是什么
JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.
JPA由三个不同的组件构成:
- 实体(Entities): 在当前版本的JPA中实体是普通Java对象(POJO)。老版本的JPA中实体类需要继承JPA提供的实体基类,但是这样的设计导致框架中存在了严重的依赖关系,测试变得更加困难;所以在新版JPA中不再要求实体类继承任何框架类。
- 对象-关系型元数据(Object-relational metadata): 应用程序的开发者们必须正确设定Java类和它们的属性与数据库中的表和列的映射关系。有两种设定方式:通过特定的配置文件建立映射;或者使用在新版本中支持的注解.
- Java持久化查询语句(Java Persistence Query Language - JPQL): 因为JPA旨在建立不依赖于特定的数据库的抽象层,所以它也提供了一种专有查询语言来代替SQL。 这种由JPQL到SQL语言的转换,为JPA提供了支持不同数据库方言的特性,使得开发者们在实现查询逻辑时不需要考虑特定的数据库类型。
2 JPA配置
如下更新pom.xml,将项目中需要的类库添加到依赖(dependencies)设置部分:
<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>
3 数据库表(Tables)
@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是可选的,示例中,我们想要所有的表都有前缀T_,所以我们设定了表名T_PERSON。表T_PERSON有3列:ID,FIRST_NAME,LAST_NAME。
这些信息是通过注解@Column和它的属性name提供给JPA中所有具有setter和getter方法的属性创建数据库列,更多的信息:
@Column(name = "FIRST_NAME", length = 100, nullable = false, unique = false)
上例中的代码指明了:限制这个字符串长度为100个字符;该列不能包含空值(null);不必是唯一的。如果试图将空值(null)作为first name插入数据库表的话,就会触发数据库约束冲突,进而导致当前事务回滚。
注解@Id和@GeneratedValue用于告诉JPA该值是主键,而且会自动生成。
运行上面的代码,Hibernate会在我们的本地H2数据库上执行如下操作:
Hibernate: drop table T_PERSON if exists
Hibernate: create table T_PERSON (id bigint generated by default as identity, FIRST_NAME varchar(255), LAST_NAME varchar(255), primary key (id))
Hibernate: insert into T_PERSON (id, FIRST_NAME, LAST_NAME) values (null, ?, ?)
4 继承(Inheritance)
完成项目的设置和上一节中的简单用例之后,我们来看一些更加复杂的用例。假设我们现在想要存储Geek们的个人信息以及他们最喜爱的编程语言信息。由于Geek也是Person,所以我们在Java模式实现中看到它是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;
}
}
首先在Geek类设置注解@Entity和@Table;然后使用Hibernate创建新表T_GEEK:
Hibernate: create table T_GEEK (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))
我们可以看到Hibernate为这两个实体创建了一个表;新增的名为DTYPE的列用于存储标志位,标识我们存储的是Person还是Geek。我们来添加一些用于持久化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_PERSON中含有如下记录(也包含我们之前已经插入的Person记录):
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
5 实体关系(Relationships)
5.1. 一对一(OneToOne)
@Entity
@Table(name = "T_ID_CARD")
public class IdCard {
private Long id;
private String idNumber;
private Date issueDate;
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "ID_NUMBER")
public String getIdNumber() {
return idNumber;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
@Column(name = "ISSUE_DATE")
@Temporal(TemporalType.TIMESTAMP)
public Date getIssueDate() {
return issueDate;
}
public void setIssueDate(Date issueDate) {
this.issueDate = issueDate;
}
}
下面定义告诉JPA每个Person含有一个确定的IDCard:
@Entity
@Table(name = "T_PERSON")
public class Person {
...
private IdCard idCard;
...
@OneToOne
@JoinColumn(name = "ID_CARD_ID")
public IdCard getIdCard() {
return idCard;
}
一对多可以自行查阅文档。
下面举几个例子来看一下如何使用jpa查询
Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);
@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
}
上面展示了一个简单的分页查询。
在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等
@Modifying
@Query("update User u set u.userName = ?1 where c.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
通过查询学生获取班级信息
public class StudentBadDto {
private Student stu;
private Classroom cla;
public StudentBadDto(Student stu, Classroom cla) {
super();
this.stu = stu;
this.cla = cla;
}
//省略了getter和setter方法
}
查询代码
@Query("select new org.konghao.model.StudentBadDto(stu,cla) from Student stu,Classroom cla where stu.cid=cla.id")
public List<StudentDto> listBadStu();
@Test
public void testListStu() {
List<StudentDto> sds = studentRepository.listBadStu();
Assert.assertEquals(5, sds.size());
Assert.assertEquals(2, sds.get(0).getStu().getId());
Assert.assertEquals(1, sds.get(0).getCla().getId());
}
下面是发出的sql
下面给出一个完整的查询方案
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
查询方法的结果可以通过关键字first或者top来限制,它们可以交替使用。在top/firest后添加数字来表示返回最大的结果数。如果没有数字,则默认假定1作为结果大小。
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
下回有时间我会从底层对jap进行讲解,期待下一次把。。。