Spring学习之旅(十二)--持久化框架

对于本职工作来说 JDBC 就可以很好的完成,但是当我们对持久化的需求变得更复杂时,如:

  • 延迟加载
  • 预先抓取
  • 级联

JDBC 就不能满足了,我们需要使用 ORM框架 来实现这些需求。

Spring 对多个持久化框架都提供了支持,包括 HibemateIBATISJPA 等。和 SpringJDBC 的支持一样, SpringORM框架 的支持除了提供与框架的继承点之外还包括一些附加服务:

  • 支持集成 Spring 声明式事务;
  • 透明的异常处理;
  • 现场安全的、轻量级的模板类;
  • DAO 支持类;
  • 资源管理

这里我们不会讲解所有的 ORM框架,只重点讲解下 JPA 的使用。


Java 持久化 API(Java Persistence API,JPA) 是基于 POJO 的持久化机制,它从 HibemateJava 对象(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 对其他持久方案的集成一样, SprignJPA 集成也提供了 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,这两种框架都能满足我们的需求,实际的选择还是看项目场景和个人使用习惯 。

原文地址:https://www.cnblogs.com/markLogZhu/p/11400498.html