SpringDataJpa学习(1)——Jpa学习
写在前面
众所周知,市面上有两大十分流行的持久层框架——Hibernate和Mybatis,之前的SSM框架就是使用的Mybatis框架,就不再过多的说了。至于Hibernate框架,他是jpa规范的实现。所谓的jpa规范,拿百度百科的解释来说,就是java官方公司规定的一套规范。Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。(百度百科)
而SpringDataJpa是Spring公司对jpa规范的又一层深度封装。因此在学习他之前有必要学习一下Jpa的使用。
jpa的核心思想——ORM
按照英文意思来说,(Object-Relational Mapping),表示对象关系映射。我们一直在使用的java语言就是一套面向对象的语言,通过orm,我们就可以把对象映射到关系型数据库。即先有实体类后有表。操作对象就可以直接操作我们的数据库表了。这种思想的出现也很好理解,很多SQL语句都是重复的,通过orm我们就可以大大地减少重复性的代码。
jpa的使用
准备工作
我们使用maven来搭建工程,导入相关的坐标:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.hibernate.version>5.0.7.Final</project.hibernate.version>
</properties>
<dependencies>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- hibernate对jpa的支持包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${project.hibernate.version}</version>
</dependency>
<!-- c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${project.hibernate.version}</version>
</dependency>
<!-- log日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- Mysql and MariaDB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
之后我们编写与实体类:
public class Customer implements Serializable {
private Long custId;
private String custName;
private String custSource;
private String custIndustry;
private String custLevel;
private String custAddress;
private String custPhone;
}
同时生成对应的get,set方法和toString方法方便我们的使用。
编写映射配置
接下来为了实现操作对象就可以操作数据库,我们要配置一下映射。
/**
* 1 实体类和表的映射关系 2 实体类中属性和表中字段的映射关系
* @author wushen
* @Entity:声明实体类
* @Table: 配置实体类和表的映射关系 name:配置数据库表的名称
*/
@Entity
@Table(name = "cst_customer")
public class Custom {
/**
* 客户的主键
* @Id:声明主键的配置
* @GeneratedValue:配置主键的生成策略
* GenerationType.IDENTITY:自增(底层数据库必须支持自动增长) mysql
* GenerationType.SEQUENCE:序列(底层数据库必须支持序列) oracle
* GenerationType.TABLE:jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
* GenerationType.AUTO:由程序自动帮助我们选择主键生成策略
* @Colunm:配置属性和字段的映射关系 name:数据库表中字段的名称
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId;
/**
* 客户名称
*/
@Column(name = "cust_name")
private String custName;
/**
* 客户来源
*/
@Column(name = "cust_source")
private String custSource;
/**
*
* 客户级别
*/
@Column(name = "cust_level")
private String custLevel;
/**
* 客户所属来源
*/
@Column(name = "cust_industry")
private String custIndustry;
/**
* 客户的联系方式
*/
@Column(name = "cust_phone")
private String custPhone;
/**
* 客户地址
*/
@Column(name = "cust_address")
private String custAddress;
其中,@Entity注解表示该类是一个实体类,@Table用来配置该实体类所对应的表名称。@Id表示该字段是一个主键,@GeneratedValue用来标注主键的生成策略,@Column用来标识该对应数据库中的哪个字段。这些配置都不是很难,详细可以查看代码中的注释。
配置JPA的核心配置文件
对于JPA的配置文件,我们需要在resources下新建一个文件夹META-INF(固定),新建一个配置文件persistence.xml(名字固定) 在里面写入相应的配置:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!-- 需要配置persistence-unit 节点
持久化单元:
name:持久化单元名称
transaction-type:事务管理的方式
JTA:分布式事务管理
RESOURCE_LOCAL:本地事务管理
-->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!-- jpa的实现方式 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 可选配置: 配置jpa实现方的配置信息 -->
<properties>
<!-- 数据库信息
用户名:javax.persistence.jdbc.user
密码:javax.persistence.jdbc.password
驱动:javax.persistence.jdbc.driver
数据库地址:javax.persistence.jdbc.url
-->
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="abc456"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpatest?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true"/>
<!-- 配置jpa实现方(hibernate)的配置信息
显示sql :false|true
自动创建数据库表:hibernate.hbm2ddl.auto
create :程序运行时创建数据库表(如果有表,先删除表再创建)
update: 程序运行时创建表(如果有表,不会创建表)
none : 不会创建表
-->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
具体的解释已经写在了注释上,请仔细看代码。
测试
在配置完了之后,我们就可以编写测试类来测试看看JPA了,如下代码:
@Test
/**
* jpa的操作步骤:
* 1.加载配置文件,创建工厂(实体管理类工厂)对象
* 2.通过实体管理类工厂获取实体管理器
* 3.获取事务对象,开启事务
* 4.完成增删改查操作
* 5.提交事务(回滚事务)
* 6.释放资源
*/
public void testSave(){
// 1.加载配置文件,创建工厂(实体管理类工厂)对象
// EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// 2.通过实体管理类工厂获取实体管理器
// EntityManager manager = factory.createEntityManager();
EntityManager manager = JpaUtils.getEntityManager();
// 3.获取事务对象,开启事务
EntityTransaction tx = manager.getTransaction();
// 开启事务
tx.begin();
// 4.保存一个客户到数据库中
Custom custom = new Custom();
custom.setCustName("武神酱");
custom.setCustIndustry("IT黑马");
// 保存
manager.persist(custom);
// 5.提交事务(回滚事务)
tx.commit();
// 6.释放资源
manager.close();
// factory.close();
}
关于这里的工具类,下面会说到。
这里可以看到我们全程没有写一行Sql语句,都是由框架自动替我们生成的。这就是Jpa规范的优点。其中这里用到了persist方法来保存,其他的例如remove(删除),merge(更新)的代码都大同小异,这里就不再赘述了。剩下的我们再重点看看两个查询方法:find和getReference:
@Test
/**
* find方法根据id查询客户
* 1. 查询的对象就是当前客户对象本身
* 2. 在调用find方法的时候,就会发送sql语句查询数据库
* 立即加载
*
*/
public void testFind(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.增删改查 -- 根据id查询客户
/**
* find: 根据id查询数据
* class:查询的结果需要包装的实体类类型的字节码
* id:查询的主键的取值
*/
Custom custom = manager.find(Custom.class, 1L);
//System.out.println(custom);
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
@Test
/**
* getReference方法根据id查询
* 1. 获取的对象是一个动态代理对象
* 2. 调用getReference方法不会立即发送sql语句查询数据库
* 当调用查询结果对象的时候,才会发送查询的sql语句,什么时候需要,什么时候发送sql语句查询数据库
* 延迟加载(懒加载)
* 得到的是一个动态代理对象
* 什么时候用,什么时候才会查询
*/
public void testReference(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.增删改查 -- 根据id查询客户
/**
* getReference: 根据id查询数据
* class:查询的结果需要包装的实体类类型的字节码
* id:查询的主键的取值
*/
Custom custom = manager.getReference(Custom.class, 1L);
System.out.println(custom);
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
可以看到,这两个查询语句类似,但getReference使用了懒加载(延迟加载)的方式,即什么时候需要什么时候再发送sql语句查询数据库,而find方法则用了立即加载的方式,即立刻发送sql语句。延迟加载的好处其实就是减轻压力,很多时候查询一堆内容是十分耗费资源的,而有时候只是查询到却没有使用,是没有必要的。延迟加载便是为了解决此类问题。
EntityManagerFactory和EntityManager
EntityManagerFactory主要是用来新建EntityManager实例的。但EntityManagerFactory的创建十分耗费资源,且是一个线程安全的对象(即多个线程访问同一个EntityManagerFactory对象不会出现线程安全问题)我们可以自己写一个工具类:
public class JpaUtils {
/**
* 程序第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,再调用方法创建EntityManager对象
* 第二次访问的时候,直接通过一个已经创建好的factory对象,创建EntityManager对象
*/
private static EntityManagerFactory factory;
static {
// 1.加载配置文件,创建entityManagerFactory
factory = Persistence.createEntityManagerFactory("myJpa");
}
/**
* 获取EntityManagerFactory对象
*/
public static EntityManager getEntityManager(){
return factory.createEntityManager();
}
}
jpql使用
jpql即java持久化查询语言,是一种以面向对象表达式语言的表达式,这种语言完全面向对象,且特征与原生sql很类似。
下面我们就来测试一下:
/**
* 查询全部
* jpql:from com.liuge.domain.Custom
* sql:select * from cst_customer
*/
@Test
public void testFindAll(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.查询全部
String jpql = "from Custom";
// 创建Query查询对象,query对象才是执行jpql的对象
Query query = manager.createQuery(jpql);
// 发送查询,并封装结果集
List list = query.getResultList();
for (Object o : list) {
System.out.println(o);
}
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
/**
* 倒序查询全部客户(id)
* sql:selct * from cst_customer order by cust_id desc
* jpql: from Custom order by custId desc
*
* 1.创建query查询对象
* 2.对参数进行赋值
* 3.查询并得到返回结果
*/
@Test
public void testOrder(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.查询全部
String jpql = "from Custom order by custId desc";
// 创建Query查询对象,query对象才是执行jpql的对象
Query query = manager.createQuery(jpql);
// 发送查询,并封装结果集
List list = query.getResultList();
for (Object o : list) {
System.out.println(o);
}
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
/**
* 使用jpql查询,统计客户的总数
* sql:select count(cust_id) from cst_customer;
* jpql:select count(custId) from Custom
*/
@Test
public void testCount(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.查询全部
String jpql = "select count(custId) from Custom";
// 1. 创建Query查询对象,query对象才是执行jpql的对象
Query query = manager.createQuery(jpql);
// 2. 对参数进行赋值
// 3. 查询并得到返回结果
Object result = query.getSingleResult();
System.out.println(result);
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
/**
* 分页查询
* sql:select * from cst_customer limit ?,?
* jpql: from Custom
*/
@Test
public void testPaged(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.查询全部
String jpql = "from Custom";
// 1. 创建Query查询对象,query对象才是执行jpql的对象
Query query = manager.createQuery(jpql);
// 2. 对参数进行赋值 --- 分页参数
// a) 起始索引 b) 每页查询的条数
query.setFirstResult(0);
query.setMaxResults(2);
// 3. 查询并得到返回结果
List list = query.getResultList();
for (Object o : list) {
System.out.println(o);
}
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
/**
* 条件查询
* 查询客户名称以"我"开头的客户
* sql:select * from cst_customer where cust_name like ?
* jpql:from Custom where custName like ?
*/
@Test
public void testCondition(){
// 1.通过工具类获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
// 2.开启事务
EntityTransaction tx = manager.getTransaction();
tx.begin();
// 3.查询全部
String jpql = "from Custom where custName like ?";
// 1. 创建Query查询对象,query对象才是执行jpql的对象
Query query = manager.createQuery(jpql);
// 2. 对参数进行赋值 --- 占位符参数
// a)索引位置:从1开始 2):取值
query.setParameter(1,"我%");
// 3. 查询并得到返回结果
List list = query.getResultList();
for (Object o : list) {
System.out.println(o);
}
// 4.提交事务
tx.commit();
// 5.释放资源
manager.close();
}
具体的解释都在代码中的注释上了。
总结
一套使用下来可以发现,jpa的使用除了繁琐的配置外,在使用时是十分方便的,基本不用写sql语句就可以实现基本的CRUD,而且复杂的CRUD也可以通过jpql实现。