什么是“持久化” 持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
什么是“持久层” 实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。 什么是ORM 即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 。
ORM的优点: 提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,能够像操作对象一样从数据库获取数据。 ORM的缺点 :ORM的缺点是会牺牲程序的执行效率和会固定思维模式。
为什么要做持久化和ORM设计(重要)
在目前的企业应用系统设计中,MVC,即 Model(模型)- View(视图)- Control(控制)为主要的系统架构模式。MVC 中的 Model 包含了复杂的业务逻辑和数据逻辑,以及数据存取机制(如 JDBC的连接、SQL生成和Statement创建、还有ResultSet结果集的读取等)等。将这些复杂的业务逻辑和数据逻辑分离,以将系统的紧耦合关系转化为松耦合关系(即解耦合),是降低系统耦合度迫切要做的,也是持久化要做的工作。MVC 模式实现了架构上将表现层(即View)和数据处理层(即Model)分离的解耦合,而持久化的设计则实现了数据处理层内部的业务逻辑和数据逻辑分离的解耦合。而 ORM 作为持久化设计中的最重要也最复杂的技术,也是目前业界热点技术。
简单来说,按通常的系统设计,使用 JDBC 操作数据库,业务处理逻辑和数据存取逻辑是混杂在一起的。 一般基本都是如下几个步骤: 1、建立数据库连接,获得 Connection 对象。 2、根据用户的输入组装查询 SQL 语句。 3、根据 SQL 语句建立 Statement 对象 或者 PreparedStatement 对象。 4、用 Connection 对象执行 SQL语句,获得结果集 ResultSet 对象。 5、然后一条一条读取结果集 ResultSet 对象中的数据。 6、根据读取到的数据,按特定的业务逻辑进行计算。 7、根据计算得到的结果再组装更新 SQL 语句。 8、再使用 Connection 对象执行更新 SQL 语句,以更新数据库中的数据。 7、最后依次关闭各个 Statement 对象和 Connection 对象。
由上可看出代码逻辑非常复杂,这还不包括某条语句执行失败的处理逻辑。其中的业务处理逻辑和数据存取逻辑完全混杂在一块。而一个完整的系统要包含成千上万个这样重复的而又混杂的处理过程,假如要对其中某些业务逻辑或者一些相关联的业务流程做修改,要改动的代码量将不可想象。另一方面,假如要换数据库产品或者运行环境也可能是个不可能完成的任务。而用户的运行环境和要求却千差万别,我们不可能为每一个用户每一种运行环境设计一套一样的系统。 所以就要将一样的处理代码即业务逻辑和可能不一样的处理即数据存取逻辑分离开来,另一方面,关系型数据库中的数据基本都是以一行行的数据进行存取的,而程序运行却是一个个对象进行处理,而目前大部分数据库驱动技术(如ADO.NET、JDBC、ODBC等等)均是以行集的结果集一条条进行处理的。所以为解决这一困难,就出现 ORM 这一个对象和数据之间映射技术。
举例来说,比如要完成一个购物打折促销的程序,用 ORM 思想将如下实现(引自《深入浅出Hibernate》): 业务逻辑如下: public Double calcAmount(String customerid, double amount){ // 根据客户ID获得客户记录 Customer customer = CustomerManager.getCustomer(custmerid); // 根据客户等级获得打折规则 Promotion promotion = PromotionManager.getPromotion(customer.getLevel()); // 累积客户总消费额,并保存累计结果 customer.setSumAmount(customer.getSumAmount().add(amount); CustomerManager.save(customer); // 返回打折后的金额 return amount.multiply(protomtion.getRatio()); } 这样代码就非常清晰了,而且与数据存取逻辑完全分离。设计业务逻辑代码的时候完全不需要考虑数据库JDBC的那些千篇一律的操作,而将它交给 CustomerManager 和 PromotionManager 两个类去完成。这就是一个简单的 ORM 设计,实际的 ORM 实现框架比这个要复杂的多
https://mp.weixin.qq.com/s/kGZewlncLwwFr4g5GJ5tPw
1. JPA是什么
2. JPA和Hibernate的关系
3. JPA的供应商
JPA 的目标之一是制定一个可以由很多供应商实现的 API,Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现,Jpa 供应商有很多,常见的有如下四种:
4. JPA的优势
5. JPA包含的技术
Spring Data 是 Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。Spring Data 具有如下特点:
Jpa 的故事
为了让大伙彻底把这两个东西学会,这里我就先来介绍单纯的Jpa使用,然后我们再结合 Spring Data 来看 Jpa如何使用。
整体步骤如下:
1.使用 IntelliJ IDEA 创建项目,创建时选择 JavaEE Persistence ,如下:
2.建成功后,添加依赖jar,由于 Jpa 只是一个规范,因此我们说用Jpa实际上必然是用Jpa的某一种实现,那么是哪一种实现呢?当然就是Hibernate了,所以添加的jar,实际上来自 Hibernate,如下:
3.添加实体类
接下来在项目中添加实体类,如下:
@Entity(name = "t_book")
public class Book {
private Long id;
private String name;
private String author;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
// 省略其他getter/setter
}
首先@Entity注解表示这是一个实体类,那么在项目启动时会自动针对该类生成一张表,默认的表名为类名,@Entity注解的name属性表示自定义生成的表名。@Id注解表示这个字段是一个id,@GeneratedValue注解表示主键的自增长策略,对于类中的其他属性,默认都会根据属性名在表中生成相应的字段,字段名和属性名相同,如果开发者想要对字段进行定制,可以使用@Column注解,去配置字段的名称,长度,是否为空等等。
4.创建 persistence.xml 文件
JPA 规范要求在类路径的 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="NewPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>org.sang.Book</class>
<properties>
<property name="hibernate.connection.url"
value="jdbc:mysql:///jpa01?useUnicode=true&characterEncoding=UTF-8"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="123"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
注意:
5.执行持久化操作
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("NewPersistenceUnit");
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Book book = new Book();
book.setAuthor("罗贯中");
book.setName("三国演义");
manager.persist(book);
transaction.commit();
manager.close();
entityManagerFactory.close();
这里首先根据配置文件创建出来一个 EntityManagerFactory ,然后再根据 EntityManagerFactory 的实例创建出来一个 EntityManager ,然后再开启事务,调用 EntityManager 中的 persist 方法执行一次持久化操作,最后提交事务,执行完这些操作后,数据库中旧多出来一个 t_book 表,并且表中有一条数据。
关于 JPQL
JPQL 举例
和在 SQL 中一样,JPQL 中的 select 语句用于执行查询。其语法可表示为:
select_clause form_clause[where_clause][groupby_clause][having_clause][orderby_clause]
其中:
在 JPQL 中,查询所有实体的 JPQL 查询语句很简单,如下:
selectofromOrdero或selectofromOrderaso
这里关键字 as 可以省去,标识符变量的命名规范与 Java 标识符相同,且区分大小写,调用 EntityManager 的 createQuery() 方法可创建查询对象,接着调用 Query 接口的 getResultList() 方法就可获得查询结果集,如下:
Query query = entityManager.createQuery( "select o from Order o");
List orders = query.getResultList();
Iterator iterator = orders.iterator();
while(iterator.hasNext() ) {
// 处理Order
}
其他方法的与此类似,这里不再赘述。
Spring Data 的故事
在 Spring Boot 中,Spring Data Jpa 官方封装了太多东西了,导致很多人用的时候不知道底层到底是怎么配置的,本文就和大伙来看看在手工的Spring环境下,Spring Data Jpa要怎么配置,配置完成后,用法和 Spring Boot 中的用法是一致的。
基本环境搭建
首先创建一个普通的Maven工程,并添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.3.RELEASE</version>
</dependency>
</dependencies>
这里除了 Jpa 的依赖之外,就是Spring Data Jpa 的依赖了。
接下来创建一个 User 实体类,创建方式参考 Jpa中实体类的创建方式,这里不再赘述。
接下来在resources目录下创建一个applicationContext.xml文件,并配置Spring和Jpa,如下:
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="org.sang"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<property name="packagesToScan" value="org.sang.model"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</prop>
</props>
</property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置jpa -->
<jpa:repositories base-package="org.sang.dao"
entity-manager-factory-ref="entityManagerFactory"/>
这里和 Jpa 相关的配置主要是三个,一个是entityManagerFactory,一个是Jpa的事务,还有一个是配置dao的位置,配置完成后,就可以在 org.sang.dao 包下创建相应的 Repository 了,如下:
public interface UserDao extends Repository<User, Long> {
User getUserById(Long id);
}
getUserById表示根据id去查询User对象,只要我们的方法名称符合类似的规范,就不需要写SQL,具体的规范一会来说。好了,接下来,创建 Service 和 Controller 来调用这个方法,如下:
@Service
@Transactional
public class UserService {
@Resource
UserDao userDao;
public User getUserById(Long id) {
return userDao.getUserById(id);
}
}
public void test1() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean(UserService.class);
User user = userService.getUserById(1L);
System.out.println(user);
}
这样,就可以查询到id为1的用户了。
Repository
上文我们自定义的 UserDao 实现了 Repository 接口,这个 Repository 接口是什么来头呢?
首先来看 Repository 的一个继承关系图:
可以看到,实现类不少。那么到底如何理解 Repository 呢?
publicinterfaceRepository<T,IDextendsSerializable>{}
@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserDao
{
User findById(Long id);
List<User> findAll();
}
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能,它的几个常用的实现类如下:
方法定义规范
1.简单条件查询
例如:定义一个 Entity 实体类:
class User{
private String firstName;
private String lastName;
}
使用And条件连接时,条件的属性名称与个数要与参数的位置与个数一一对应,如下:
findByLastNameAndFirstName(String lastName,String firstName);
查询举例: 1.按照id查询
User getUserById(Long id);
User getById(Long id);
2.查询所有年龄小于90岁的人
List<User> findByAgeLessThan(Long age);
3.查询所有姓赵的人
List<User> findByUsernameStartingWith(String u);
4.查询所有姓赵的、并且id大于50的人
List<User> findByUsernameStartingWithAndIdGreaterThan(String name, Long id);
5.查询所有姓名中包含"上"字的人
List<User> findByUsernameContaining(String name);
6.查询所有姓赵的或者年龄大于90岁的
List<User> findByUsernameStartingWithOrAgeGreaterThan(String name, Long age);
7.查询所有角色为1的用户
List<User> findByRole_Id(Long id);
2.支持的关键字
支持的查询关键字如下图:
3.查询方法流程解析
为什么写上方法名,JPA就知道你想干嘛了呢?假如创建如下的查询: findByUserDepUuid()
,框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc:
Page<UserModel> findByName(String name, Pageable pageable);
List<UserModel> findByName(String name, Sort sort);
@Query注解
有的时候,这里提供的查询关键字并不能满足我们的查询需求,这个时候就可以使用 @Query 关键字,来自定义查询 SQL,例如查询Id最大的User:
@Query("select u from t_user u where id=(select max(id) from t_user)")
User getMaxIdUser();
如果查询有参数的话,参数有两种不同的传递方式,
1.利用下标索引传参,索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致:
@Query("select u from t_user u where id>?1 and username like ?2")
List<User> selectUserByParam(Long id, String name);
2.命名参数(推荐):这种方式可以定义好参数名,赋值时采用@Param("参数名"),而不用管顺序:
@Query("select u from t_user u where id>:id and username like :name")
List<User> selectUserByParam2(@Param("name") String name, @Param("id") Long id);
查询时候,也可以是使用原生的SQL查询,如下:
@Query(value = "select * from t_user",nativeQuery = true)
List<User> selectAll();
@Modifying注解
涉及到数据修改操作,可以使用 @Modifying 注解,@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如涉及某些字段更新时最为常用,示例如下:
@Modifying
@Query("update t_user set age=:age where id>:id")
int updateUserById(@Param("age") Long age, @Param("id") Long id);
注意:
说到这里,再来顺便说说Spring Data 中的事务问题: