Spring学习之旅(十二)--持久化框架
对于本职工作来说 JDBC 就可以很好的完成,但是当我们对持久化的需求变得更复杂时,如:
- 延迟加载
- 预先抓取
- 级联
JDBC 就不能满足了,我们需要使用 ORM框架 来实现这些需求。
Spring 对多个持久化框架都提供了支持,包括 Hibemate、IBATIS、JPA 等。和 Spring 对 JDBC 的支持一样, Spring 对 ORM框架 的支持除了提供与框架的继承点之外还包括一些附加服务:
- 支持集成 Spring 声明式事务;
- 透明的异常处理;
- 现场安全的、轻量级的模板类;
- DAO 支持类;
- 资源管理
这里我们不会讲解所有的 ORM框架,只重点讲解下 JPA 的使用。
Java 持久化 API(Java Persistence API,JPA) 是基于 POJO 的持久化机制,它从 Hibemate 和 Java 对象(Java Data Object,JDO) 上借鉴了很多理念并加入了 Java5 注解的特性。
引入依赖
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- mysql连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- JPA 依赖-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.17.Final</version>
</dependency>
</dependencies>
配置实体管理器工厂
基于 JAP 的应用程序需要使用 EntityManagerFactory 的实现类来获取 EntityManager 实例。 JAP 定义了两种类型的实体管理器:
- 应用程序管理类型(Application-managed): 当应用程序向实体管理器直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在 Java EE 容器中的独立应用程序。
- 容器管理类型(Container-managed): 实体管理器由 Java EE 创建和管理。应用程序根本不与实体管理工厂打交道。这种类型的实体管理器最适合用于 Java EE 容器。
这两种实体管理器工厂分别由对应的 Spring 工厂对象创建:
- LocalEntityManagerFactoryBean: 生成应用程序管理类型的 EntityManager-Factory
- LocalContainerEntityManagerFactoryBean: 生成容器管理类型的 EntityManager-Factory
配置应用程序管理类型的 JAP
对于应用管理类型的实体管理工厂,它的绝大部分配置信息来源于一个名为 persistence.xml 的配置文件,这个文件必须位于类路径下的 META-INF 目录下。
persistence.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="jpa" transaction-type="RESOURCE_LOCAL">
<!-- 实体类 -->
<class>com.marklogzhu.web.entity.User</class>
<properties>
<!-- 数据库连接的基本信息 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/learn"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="sa000"/>
</properties>
</persistence-unit>
</persistence>
在 persistence.xml 文件中已经包含了多个配置信息,所以在 Spring 中的配置就很少了。
@Bean
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
LocalEntityManagerFactoryBean entityManagerFactoryBean = new LocalEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName("jpa");
return entityManagerFactoryBean;
}
配置容器管理类型的 JAP
当运行在容器中时,通过容器提供的信息来生成 EntityManagerFactory。这样做的好处就是可以不用配置 persistence.xml 文件了,只需要在 Spring 上下文中配置。
@Autowired
private DataSource dataSource;
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
// 扫描指定包下带有 @Entity注解的实体类等同于 persistence.xml 文件 <class> 标签
emfb.setPackagesToScan("com.marklogzhu.web.entity");
return emfb;
}
除了配置 ** dataSource ** 属性之外,我们还配置了一个 jpaVendorAdapter 属性,它用于指定使用哪一个厂商的 JAP 实现:
- EclipseLinkJpaVendorAdapter
- HibernateJpaVendorAdapter
- OpenJpaVendorAdapter
- TopLinkJpaVendorAdapter(Spring 3.1 版本已废弃)
我们这边采用 HibernateJpaVendorAdapter 实现类:
@Bean
public JpaVendorAdapter jpaVendorAdapter(){
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
// 设置数据库类型
adapter.setDatabase(Database.MYSQL);
// 设置打印sql语句
adapter.setShowSql(Boolean.TRUE);
// 设置不生成ddl语句
adapter.setGenerateDdl(Boolean.FALSE);
// 设置hibernate方言
adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return adapter;
}
编写基于 JPA 的 Repository
就像 Sprign 对其他持久方案的集成一样, Sprign 对 JPA 集成也提供了 JpaTemplate 模板以及对象的支持类 JpaDaoSupport,但是为了更纯粹的 JPA 方式,基于模板的 JPA 已经弃用了,所以我们这里只讲纯粹的 JPA 方式。
接口:
public interface UserRepository {
User findById(Long id);
}
实现:
@Repository("userRepository")
public class UserJpaRepository implements UserRepository {
@PersistenceContext
private EntityManager em;
@Override
public User findById(Long id) {
return em.find(User.class,id);
}
}
用户实体类:
@Entity
@Table(name = "sys_user")
public class User {
@Id
private Long id;
private String avatar;
private String account;
private String password;
private String salt;
private String name;
private String birthday;
private Integer sex;
private String email;
private String phone;
@Column(name = "role_id")
private String roleId;
@Column(name = "dept_id")
private Long deptId;
private Integer status;
private Date createtime;
private Long version;
//get/set......
}
单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RootConfig.class)
public class JpaDaoTest {
@Autowired
private UserRepository userRepository;
@Test
public void testFindByUserId(){
Long userId = new Long(1);
User user = userRepository.findById(userId);
Assert.assertNotNull(user);
Assert.assertEquals(userId,user.getId());
}
}
注解说明
- @Repository: 用于标注数据访问组件,即DAO组件
- @PersistenceContext: 注入 EntityManager 对象
- @Entity: 表示这个类是一个实体类
- @Table(name = "sys_user") : 数据库中表名是 sys_user ,而我们的实体类名称是 User 两者不一致所以需要设置绑定关系,设置方法有如下两种:
- 如实例中通过 @Column 注解 做显式转换
- 修改默认命名策略为 PhysicalNamingStrategyStandardImpl
借助 Spring Data 实现自动化的 JPA Repository
上面的 UserJpaRepository 实现的是我们自己申明的方法,不同的 Repository 除了操作的对象不同外,其他方法都是一样的,而借助 Spring Data 我们就可以省略这些通用的方法。
配置 Spring Data JPA
@Configuration
@EnableJpaRepositories("com.marklogzhu.web.dao")
public class JpaConfiguration {
}
使用 @EnableJpaRepositories 注解来扫描指定包下的 Repository 。我们回到 UserRepository 接口:
public interface UserRepository extends JpaRepository<User,Long> {
}
可以看到现在 UserRepository 接口继承了 JpaRepository接口,而 JpaRepository 又扩展自 Repository 接口。所以就可以直接使用 Spring Data JPA 默认提供的18个方法:
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
将 UserJpaRepository 类删除,我们不需要自己实现的接口类了。
修改单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RootConfig.class)
public class JpaDaoTest {
@Autowired
private UserRepository userRepository;
@Test
public void testFindByUserId(){
Long userId = new Long(1);
User user = userRepository.findById(userId).get();
Assert.assertNotNull(user);
Assert.assertEquals(userId,user.getId());
}
}
查询方法
除了上面的接口定义方法之后,我们还能新增查询方法来实现需求。
接口定义:
@Transactional
public interface UserRepository extends JpaRepository<User,Long> {
User findUserByAccount(String account);
}
单元测试:
@Test
public void testFindByAccount(){
String account = "admin";
User user = userRepository.findUserByAccount(account);
Assert.assertNotNull(user);
Assert.assertEquals(account,user.getAccount());
}
可以看到我们并没有自己实现 findByAccount 方法,但是却可以正常使用。这是因为 Spring Data JPA 会根据我们定义的方法来推断我们需要的功能。它的原理是 Spring Data 定义了一组小型的领域特定语言(domain-speclific language,DSL)来用推断我们方法的作用。
findUserByAccount
- find :查询动词
- User: 主题,大部分场景下可以省略,但是如果以 Distinct 开头的话,它表示返回的结果不包含重复的记录。
- Account:断言,会有一个或多个限制结果的条件。每个条件必须引用一个属性并且可以指定一个比较操作,如果忽略操作的话就默认是相等比较操作。
方法定义规则
符号 | 作用 |
---|---|
And | 并且 |
Or | 或 |
Is,Equals | 等于 |
Between | 两者之间 |
LessThan | 小于 |
LessThanEqual | 小于等于 |
GreaterThan | 大于 |
GreaterThanEqual | 大于等于 |
After | 之后(时间)> |
Before | 之前(时间)< |
IsNull | 等于Null |
IsNotNull,NotNull | 不等于Null |
Like | 模糊查询。查询件中需要自己加% |
NotLike | 不在模糊范围内。查询件中需要自己加% |
StartingWith | 以某开头 |
EndingWith | 以某结束 |
Containing | 包含某 |
OrderBy | 排序 |
Not | 不等于 |
In | 某范围内 |
NotIn | 某范围外 |
TRUE | 真 |
FALSE | 假 |
IgnoreCase | 忽略大小写 |
自定义查询方法
除了上面这种符合 DSL 规范的方法命名外,我们也可以自定义不符合命名约定的方法。在方法上面使用 @Query 注解 来为 Spring Data 提供要执行的查询。
方法定义:
@Query("select name from User where id =:userId")
String getUserName(@Param("userId")Long userId);
单元测试:
@Test
public void testGetUserName(){
Long userId = new Long(1);
String userName = userRepository.getUserName(userId);
Assert.assertEquals("admin",userName);
}
注:语句中表名应该是 ORM 映射的类名,而不是实际的表名
除了持久化框架 SpringJPA 外,比较常用的还有半持久化框架 Mybatis,这两种框架都能满足我们的需求,实际的选择还是看项目场景和个人使用习惯 。
Spring学习之旅(十二)--持久化框架的更多相关文章
- Spring学习之旅(十)--MockMvc
在之前的 Spring学习之旅(八)--SpringMVC请求参数 我们是通过在控制台输出来验证参数是否正确,但是这样做实在是太耗时间了,我们今天来学习下 MockMvc,它可以让我们不需要启动项目就 ...
- Spring学习之旅(二)极速创建Spring框架java Web工程项目
编译工具:eclipse 1)创建Web工程:spring_web_helloworld 2)导入所需jar包: 3)创建实体类:同上篇博文 4)创建配置文件hellobean.xml.同上篇博文 不 ...
- Java框架spring 学习笔记(十二):aop实例操作
使用aop需要在网上下载两个jar包: aopalliance.jar aspectjweaver.jar 为idea添加jar包,快捷键ctrl+shift+alt+s,打开添加jar包的对话框,将 ...
- Spring学习之旅(十五)--SpringBoot
在使用 Spring 的过程中,有时候会出现一些 ClassNotFoundException 异常,这是因为 JAR 依赖之间的版本不匹配所导致的.而 Spring Boot 就能避免绝大多数依赖版 ...
- Spring学习之旅(十四)--缓存
数据库的读写并发一直都是应用性能的瓶颈所在之一,针对改动频率很小的数据我们应该将他存放到缓存中,减少与数据库的交互. 启用对缓存的支持 Spring 对缓存的支持有两种方式: 注解驱动的缓存 XML ...
- Spring+SpringMVC+MyBatis深入学习及搭建(十二)——SpringMVC入门程序(一)
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6999743.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(十一)——S ...
- Java框架spring 学习笔记(十八):事务管理(xml配置文件管理)
在Java框架spring 学习笔记(十八):事务操作中,有一个问题: package cn.service; import cn.dao.OrderDao; public class OrderSe ...
- Spring学习之旅(八)Spring 基于AspectJ注解配置的AOP编程工作原理初探
由小编的上篇博文可以一窥基于AspectJ注解配置的AOP编程实现. 本文一下未贴出的相关代码示例请关注小编的上篇博文<Spring学习之旅(七)基于XML配置与基于AspectJ注解配置的AO ...
- Spring Boot 2.X(十二):定时任务
简介 定时任务是后端开发中常见的需求,主要应用场景有定期数据报表.定时消息通知.异步的后台业务逻辑处理.日志分析处理.垃圾数据清理.定时更新缓存等等. Spring Boot 集成了一整套的定时任务工 ...
随机推荐
- MyEclipse2014破解版
百度云:链接:http://pan.baidu.com/s/1c3jKMa 密码:yss0 等版本)后,不要打开软件. 二.解压破解文件压缩包,得到一下文件列表: 三.双击run.bat,即可运行cr ...
- (原创)将Datatable数据按照Excel模板格式导出
最近遇到一个问题,就是导出数据的时候需要自定义的表头,如图 如果自己用代码写表头的话,可能会有点复杂,而且代码量很多,所以我就想了一个办法,直接在Excel里面把表头定义好,然后把数据写入Excel模 ...
- python的乘法口诀表
python的乘法口诀表 python的乘法口诀表 用python来写一个脚本,使得这个脚本在运行后自动输出乘法口诀表. pyton的脚本如下: #!/usr/bin/env python #codi ...
- 摘录C#代码片段
1.保存数据到指定文件String response = "";//数据 byte[] bytes = response.getBytes();//转化Byte string fi ...
- web设计_5_自由的框式组件
1. CSS3 border-radius 圆角矩形框 圆角矩形框组件是页面布局中常常用到的,利用CSS3的border-radius可非常方便的创建. 并且在横向纵向上面都有很好的扩展性和灵活性. ...
- HTTP文件上传原理
前言 对于这块知识点,我一直都是模糊的,不是非常清楚的.在平时的工作中,遇到上传的问题,也没有深入的去研究过,也都是直接用别人封装好的类来完成自己的工作.某一天,看了本书,说到这个知识点,一脸茫然,觉 ...
- 【iOS】No suitable application records found
昨天提交 Apple 审核时遇到这个问题,如图: 原来是还没在 iTunes Connect 创建 APP ... 一时着急大意了…… 后来想想还真是脑子一时没反应过来……
- 浅析scrapy与scrapy_redis区别
最近在工作中写了很多 scrapy_redis 分布式爬虫,但是回想 scrapy 与 scrapy_redis 两者区别的时候,竟然,思维只是局限在了应用方面,于是乎,搜索了很多相关文章介绍,这才搞 ...
- 用大白话告诉你 :Java 后端到底是在做什么?
阅读本文大概需要 6 分钟. 作者:黄小斜 新手程序员通常会走入一个误区,就是认为学习了一门语言,就可以称为是某某语言工程师了.但事实上真的是这样吗?其实并非如此. 今天我们就来聊一聊,Java 开发 ...
- JAVA并发之阻塞队列浅析
背景 因为在工作中经常会用到阻塞队列,有的时候还要根据业务场景获取重写阻塞队列中的方法,所以学习一下阻塞队列的实现原理还是很有必要的.(PS:不深入了解的话,很容易使用出错,造成没有技术深度的样子) ...