一、JPA
1. JPA 介绍
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一 。 其实在5.0出现发布之前,市面上有诸如、Hibernate 、OpenJPA 、TopLink 等一些列 ORM框架,并且hibernate已经很受大众喜爱了,后来hibernate在3.2版本正式接入JPA 规范,表示自己即属于JPA的框架的具体实现。 一句话概括: JPA 是一种规范、 Hibernate 是这种规范的最好实现。
Dao最受欢迎的两个框架: MyBatis ----不是JPA体系 ----- 需要写sql语句 | Hibernate --- JPA 体系 -----可以不用写sql语句
2. ORM介绍
Object Relational Mapping : 对象关系映射 。 ORM的思想其实就是让 表 和 JavaBean 形成一种映射关系 , 并且让表里面的字段 和JavaBean 里面的成员形成映射关系。
3. JPA 入门
此处就以一个简单的保存案例来实现JPA 入门吧。
- 添加JPA 依赖
// 在maven 搜索关键字 persistence 即可 hibernate的核心依赖,会包含它对JPA规范的升级,所以这个依赖可以不加
//compile group: 'javax.persistence', name: 'persistence-api', version: '1.0'
//不要忘记了添加mysql数据库依赖
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'
//下面要添加hibernate的依赖,因为JPA 只是一套规范而已,不是具体的实现,JPA的实现有很多,
//这里采用hibernate作为我们的实现,所以需要添加hibernate的依赖库
//compile group: 'org.hibernate', name: 'hibernate-core', version: '4.3.9.Final'
//hibernate实体管理者,对接JPA规范的管理员 这是hibernate为了迎合JPA的规范做出来的。
compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '4.3.9.Final'
- 定义实体类
@Entity(name="t_user")
public class User {
private static final String TAG = "User";
private int id;
private String name;
private int age ;
@Id
@GeneratedValue
public int getId() {
return id;
}
//剩下的get 和 set方法
...
}
- 定义配置文件
必须要说明一点,配置文件的名字需要固定,必须叫:persistence.xml , 而且也必须放在 META-INF下面,不能直接放在resource下面。需要在resource下面,新建META-INF 文件夹,然后在放置 xml文件
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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_2_0.xsd">
<!--持久化单元,数据库 & 实体类 & 具体实现方式配置单元-->
<persistence-unit name="user">
<!--声明提供者,也就是说具体实现,因为JPA 只是规范,这里使用hibernate作为具体实现-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--表示实体类有哪些-->
<class>com.itheima.bean.User</class>
<!--数据库连接参数-->
<properties>
<!--使用jpa的配置-->
<!--<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/test"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="root"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> -->
<!--也可以使用hibernate的配置为了以后无缝对接,还是建议使用上面的规范化的JPA 配置-->
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<!--表示自动建表-->
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
- 测试代码
@Test
public void testJPA(){
//创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");
//创建实体管理员
EntityManager manager = factory.createEntityManager();
//获取事务对象
EntityTransaction transaction = manager.getTransaction();
//开启事务
transaction.begin();
//构建实体对象
User user = new User();
user.setName("zhangsan");
user.setAge(18);
//持久化,也就是保存到数据库
manager.persist(user);
//提交事务
transaction.commit();
//关闭管理员
manager.close();
//关闭工厂,一般不关闭,后面也不用关心这个了。
factory.close();
}
4. 入门详解
a. 注解解释
@Entity : 表示该类是一个实体,也就是和表形成映射,可以添加属性来指定对应的表
如:@Entity(name="t_user")
@Table : 指定表名,可以不写,可以在entity注解上直接表示标识表名。
如 : @Table(name="t_user")
@Id : 用于表示主键,指定哪一个属性和主键对应 可以在变量上声明,也可以在get方法上声明,建议统一。
@GeneratedValue : 用于表示主键生成策略 主键生成策略,提供的有4种
- 主键策略解释
1、AUTO 自动选择一个最适合底层数据库的主键生成策略。如MySQL会自动对应auto increment。这个是默认选项,即如果只写@GeneratedValue,等价于@GeneratedValue(strategy=GenerationType.AUTO)。注意在mysql5 + hibernate 5的版本测试下,会产生另外一张表,用于记录主键值。 可以在核心配资文件中添加此属性来达到native效果
<property name="hibernate.id.new_generator_mappings">false</property>
2、IDENTITY 表自增长字段,Oracle不支持这种方式。
3、SEQUENCE 通过序列产生主键,MySQL不支持这种方式。
4、TABLE 通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。不同的JPA实现商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表,Hibernate生成一个hibernate_sequences表,而TopLink则生成sequence表。这些表都具有一个序列名和对应值两个字段,如SEQ_NAME和SEQ_COUNT。
- 关于UUID策略
通常我们建表,主键都是int类型,但是其实在开发的时候,有些表也会建成String类型,也就是varchar类型 , 并且有时候我们要主键唯一,这时候需要使用到uuid策略了。JPA并没有提供UUID 策略, 好在hibernate提供了这种策略的支持。
//声明uuid这种策略类型。然后给定一个别名,叫做jpa-uuid 这个注解是hibernate提供的注解, jpa的注解没有
@GenericGenerator(name="jpa-uuid" ,strategy="uuid")
//表示对应 t_student 这个表
@Entity(name="t_student")
public class Student {
private String id;
private String name;
private int age;
private String phone;
private String address;
//指定使用你的主键策略是 jpa-uuid这个名称对一个你的类型,其实就是指定了主键是uuid 。
//由hibernate来维护主键。
@Id
@GeneratedValue(generator="jpa-uuid")
public String getId() {
return id;
}
...
}
b. xml配置解释
- xml必须放在META-INF下面,名字也必须固定是persistence.xml
- xml其实就是用于表示怎么连接数据库,具体干活的是JPA实现是什么, 以及有哪些映射实体类。
- 具体的属性名称,可以参考pdf文档。 第八章节
- 关于hibernate的配置,请参考hibernate的pdf文档
c. 测试代码解释
代码没有什么好解释的了,必须要有实体管理员工厂,然后获取到管理员,后续就可以操作了。 可以稍稍解释下即可
5. CRUD
JPA 有一个要求就是写入操作,需要使用事务,否则数据不会到达数据库。
1. 增加
@Test
public void testJPA(){
//创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");
//创建实体管理员
EntityManager manager = factory.createEntityManager();
//获取事务对象
EntityTransaction transaction = manager.getTransaction();
//开启事务
transaction.begin();
//构建实体对象
User user = new User();
user.setName("zhangsan");
user.setAge(28);
//持久化,也就是保存到数据库
manager.persist(user);
//提交事务
transaction.commit();
//关闭管理员
manager.close();
//关闭工厂,一般不关闭,后面也不用关心这个了。
factory.close();
}
2. 删除
需要删除某条记录,需要先把它查询出来,然后才能删除。
@Test
public void testDelete(){
//创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");
//创建实体管理员
EntityManager manager = factory.createEntityManager();
//获取事务对象
EntityTransaction transaction = manager.getTransaction();
//开启事务
transaction.begin();
//查询主键为1的用户
User u = manager.find(User.class,1);
//删除用户
manager.remove(u);
//提交事务
transaction.commit();
//关闭管理员
manager.close();
//关闭工厂,一般不关闭,后面也不用关心这个了。
factory.close();
}
3. 修改
修改也一样,需要先查询,然后修改后,在持久化。
@Test
public void testUpdate(){
//创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");
//创建实体管理员
EntityManager manager = factory.createEntityManager();
//获取事务对象
EntityTransaction transaction = manager.getTransaction();
//开启事务
transaction.begin();
//查询主键为1的用户
User u = manager.find(User.class,2);
u.setAge(102);
//这句可以不写,因为查询出来的user对象已经是持久态,直接操作它也可以修改数据库
// manager.persist(u);
/* //删除用户
manager.remove(u);*/
//提交事务
transaction.commit();
//关闭管理员
manager.close();
//关闭工厂,一般不关闭,后面也不用关心这个了。
factory.close();
}
4. 查询
@Test
public void testQuery(){
//创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");
//创建实体管理员
EntityManager manager = factory.createEntityManager();
//不支持写*
Query query = manager.createQuery("select t from User t");
List<User> list = query.getResultList();
System.out.println("list=" + list);
//关闭管理员
manager.close();
//关闭工厂,一般不关闭,后面也不用关心这个了。
factory.close();
}
6. 对象时态
这个状态主要是针对我们操作的持久化类对象。 有四种状态
- 瞬时态
对象仅仅是创建出来(new对象),没有和EntityManager 产生关系
- 持久态
和EntityManager建立关系,被持久化,保存到数据库或者刚从数据库查询出来。
- 脱管态
已经持久化过了,现在要脱离管理。 提交事务,或者清除EntityManager都会走向这个状态。
- 删除态
该状态只有在JPA 范畴里面才有,单独拿hibernate来说,没有这个状态。只有调用了EntityManager
的remove方法,才会走到这个状态。
7. 多表关系确立
最好使用例子引出关系确立的重要性。
a. 多对一
此处以分类 & 商品的关系来解释
- 分类 Category
@Entity(name="category")
public class Category {
private int id;
private String name;
@Id
@GeneratedValue
public int getId() {
return id;
}
//剩下的get & set方法
...
}
- 商品 Product
商品在这个关系里面扮演的是外键的关系,是多方的角色,也就是一种商品分类,可以有很多件商品。所以从商品的位置出发,它和分类的关系是多对一的关系。
@Entity(name="product")
public class Product {
private int id ;
private String name;
private double price;
//表示该商品属于哪一种分类。
private Category category;
@Id
@GeneratedValue
public int getId() {
return id;
}
// @ManyToOne表示是多对一关系 optional =false 表示该外键不能为空
@ManyToOne(optional = false)
//@JoinColumn name=cid 通俗的意思是: 拿什么列来作为外键,
//这里指定cid , 表示生成的是外键名称叫做cid
@JoinColumn(name="cid")
public Category getCategory() {
return category;
}
...
}
b. 一对多
一对多,比前面的多对一稍微麻烦一点,而且一对多必须双方都配上注解,否则生成的关系会比较乱。
- 分类 Category
区别只是一方 category 里面多了一个注解
@OneToMany
@Entity(name="category")
public class Category {
private int id;
private String name;
private Set<Product> productSet = new HashSet<Product>();
@Id
@GeneratedValue
public int getId() {
return id;
}
//mappedBy 表示和谁形成映射关系,也表示谁来维护这层关系。
@OneToMany(mappedBy = "category")
public Set<Product> getProductSet() {
return productSet;
}
//剩下的get & set 方法
...
}
- 商品 Product
@Entity(name="product")
public class Product {
private int id ;
private String name;
private double price;
//表示该商品属于哪一种分类。
private Category category;
@Id
@GeneratedValue
public int getId() {
return id;
}
@ManyToOne(optional = false)
@JoinColumn(name="cid")
public Category getCategory() {
return category;
}
//剩下的get & set方法
...
}
8 . 查询
- 对象导航查询
所谓对象导航查询的意思是: 如果A表和B表存在主外键关系(一对多 | 多对一 ),那么在查询A表的时候, 也会顺便查询与该条记录关联的B表记录信息。如分类和商品的关系, 如果查询手机分类,那么会顺便把商品表中属于手机分类的商品给查询出来。
二、SpringData JPA
1. 介绍
在JavaEE 5.0发布的时候,sun公司就提出了jpa的规范,希望整合ORM技术,实现天下归一 。 虽然我们学过的orm技术只有hibernate、但是其实orm技术还有其他的一些方案,比如Apache的 openJPA。 Spring 作为业务逻辑层框架,起到承上启下的作用。所以它对JPA的这些技术实现做了一套封装。大家只要按照规范来配置即可,甚至你们的dao层实现都不用实现了,它在内部给你实现,如果我们想换到其他的jpa实现方案,那么只需要修改配置即可。 这就是我们要说的Spring Data JPA。
JPA
Hibernate | Open Jap | Toplink ....
Spring Data JPA ---> 对Dao层的代码再一次升级封装 。 统一的JPA的实现,在封装。
2. 入门
1. 搭建环境 (建表)
- 添加依赖
//mysql驱动
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'
//springboot 依赖
compile("org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE")
//spring data jpa 注意: 不要引入错了,要找的组是springboot的依赖。如下面这条注释的是错误的。
//因为我们使用了SpringBoot,所以采用的库,也要是springboot组下的。
//compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.3.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.10.RELEASE'
- 添加配置文件
同样还是那个
application.properties
, 位于resources
下面
#连接数据库
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#hibernate配置
# 自动建表 update:表示有表则直接使用,无表就新建
spring.jpa.hibernate.ddl-auto=update
# 表示在操作时,输出sql语句
spring.jpa.show-sql=true
- 实体类
//name :用于表示构建出来的表名
@Entity(name="t_user")
public class User {
private static final String TAG = "User";
private int id;
private String name;
private int age ;
@Id
@GeneratedValue
public int getId() {
return id;
}
//剩余的get & set方法代码
...
}
2. 添加数据
上面仅仅是完成了表的创建工作,实际上还并没有看出来
SpringData JPA
有什么厉害之处,现在就要往表里面添加数据了。
- controller
@RestController
public class UserController {
//这里要注入业务逻辑层引用
@Autowired
private UserService userService;
@RequestMapping("/save")
public String save(){
User user = new User();
user.setName("奥巴马");
user.setAge(58);
//调用业务逻辑层
userService.save(user);
return "save success~!";
}
}
- service
public interface UserService {
void save(User user);
}
---------------------------------------------------------------
@Service
@Transactional
public class UserServiceImpl implements UserService {
//这里要注入UserDao引用
@Autowired
private UserDao userDao;
@Override
public void save(User user) {
userDao.save(user);
}
}
- dao
注意: dao层只有一个接口而已,方法没有,其实是父类
CrudRepository
已经定义了一些常用的方法,我们可以直接拿过来用即可。真正我们在业务逻辑层拿到的是 SimpleJPARepository 这个类的实例。它其实就是实现了CrudRepository接口方法。
public interface UserDao extends CrudRepository<User, Integer> {
}
三、 通用接口介绍
经过上面的入门例子,大家也看到了,spring data对于我们编程确实方便了许多。我们无需关心dao层的实现,只需要简单的照着规则去写代码即可,spring data jpa 最重要的就是我们的dao层继承的接口。接下来给大家着重说明它的几个接口。
1. Repository
这个接口是所有接口的父接口,也就是它就是老大了。所有我们后面用的接口都是从这位兄弟身上扩展来的。 下图是Repository 这个接口的继承体系,图中的UserDao 和 MyRepository 是我自己写的,大家不用理会。 不过要声明的是: 这个Repository 是一个空接口!!!,里面没有任何方法.
package org.springframework.data.repository;
import java.io.Serializable;
/**
* Central repository marker interface. Captures the domain type to manage as well as the domain type's id type. General
* purpose is to hold type information as well as being able to discover interfaces that extend this one during
* classpath scanning for easy Spring bean creation.
* <p>
* Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the
* same signature as those declared in {@link CrudRepository}.
*
* @see CrudRepository
* @param <T> the domain type the repository manages
* @param <ID> the type of the id of the entity the repository manages
* @author Oliver Gierke
*/
public interface Repository<T, ID extends Serializable> {
}
这个repository虽然是个空接口,我们自己来继承它,虽然没有任何要实现的方法,但是我们可以自己写,自己声明方法,这也是允许D~~ ,话不多说,走起~~~
- 自定义接口 MyRepository
public interface MyRepository extends Repository<User, Integer> {
/**
* 根据用户id查询用户
* @param uid
* @return
*/
User findByUid(int uid);
}
- service层代码
public interface UserService {
User findByUid(int uid);
}
------------------------------
@Service
@Transactional //现在仍然是查询这个注解可以不写,为了免得大家记太多,我就索性都打开了。
public class UserServiceImpl implements UserService {
@Autowired
private MyRepository repository;
@Override
public User findByUid(int uid) {
return repository.findByUid(uid);
}
}
- 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
@Autowired
private UserService userService;
@Test
public void testFindByUid(){
System.out.println(userService.findByUid(1));
}
}
- 结果:
- 疑问:
写到这,有的同学绝对会有疑问, 这都可以,那这也行的话,我写一个叫做
fUid()
或者叫做zhaoUserByUid()
这样的方法名,行不行呢? 答案是: NO!因为我们声明的是接口,具体的实现类还是由人家的spring做出来的。spring胃口比较挑剔,而且为了满足大众化的口味,就做出了一些命名上的要求,大家要慢慢习惯这种要求,这也正是它的另一个框架Spring Boot的口号
习惯优于配置
。 下图摘自 Spring Data JPA 文档对方法关键字的描述。如:我们想按照用户的
name
查询, 那么可以写成findByName
以此类推
如果觉得这些关键字记不住,那么spring可以允许我们程序员自己定义自己的方法名。
2. 自定义方法名
这个小节主要讲述的是,我们可以自己定义自己的方法名,你爱叫啥叫啥,随意。
- Dao接口
public interface MyRepository extends Repository<User, Integer> {
@Query("from User where uid = ?1") //?1 表示取第一个参数uid 来替代这个?。
User selectUser(int uid);
}
- Service
public interface UserService {
User selectUser(int uid);
}
- 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
@Autowired
private UserService userService;
@Test
public void testSelectUser(){
System.out.println(userService.selectUser(1));
}
}
虽然我们可以自己定义方法名,但是这种做法稍微有点麻烦,所以建议少用这种写法。 只有在以后执行多表查询的时候,我们才需要这么做。
3. CrudRepository
这个接口是扩展了repository, 在这个接口里面默认给大家声明好了一些增删改查的方法,此处给大家演练一个查询所有用户的操作
- Dao
public interface UserDao extends CrudRepository<User, Integer> {
//查询所有的用户
}
- Service
@Service
//@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public List<User> findAll() {
return (List<User>) userDao.findAll();
}
}
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
@Autowired
private UserService userService;
@Test
public void testFindAll(){
List<User> list= userService.findAll();
System.out.println("list="+list);
}
}
4.PagingAndSortingRepository
这个接口是是CrudRepository的扩展接口,除了兼备增删改查之外,它还扩展了分页查询&排序的效果。
以下是这个接口的代码声明
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
/**
* Returns all entities sorted by the given options.
*
* @param sort
* @return all entities sorted by the given options
*/
Iterable<T> findAll(Sort sort);
/**
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
*
* @param pageable
* @return a page of entities
*/
Page<T> findAll(Pageable pageable);
}
- Dao
public interface UserDao extends PagingAndSortingRepository<User, Integer> {
//查询所有的用户
}
- Service
public interface UserService {
/**
* 返回值是page 这个类型是springdatajpa定义好的。其实就和
* 我们自己平常写的PageBean 一样。
* @param pageable
* @return
*/
Page<User> findByPage(Pageable pageable);
}
---------------------------------------------------------
@Service
//@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public Page<User> findByPage(Pageable pageable) {
return userDao.findAll(pageable);
}
}
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
@Autowired
private UserService userService;
@Test
public void testFindByPage(){
//这里ctrl + T 看实现类,然后找PageRequest ,别说你不会哈~~
// page : page表示请求的页码数, 从0开始,0就代表第一页大家。
//size : 表示每页拿多少条记录
Pageable pageable = new PageRequest(1, 2);
Page<User> page = userService.findByPage(pageable);
//当前是第几页 这个页码从0开始的,大家可以加上1表示从1开始。
System.out.println(page.getNumber());
System.out.println(page.getTotalPages()); //总页数
System.out.println(page.getSize()); //每页个数
System.out.println(page.getTotalElements()); //总记录数
System.out.println(page.getContent()); //总页数
}
}
重点:
多表关系建立
@ManyToOne @OneToMany
SpringDataJPA 完成CRUD
CrudRepository