zoukankan      html  css  js  c++  java
  • 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,这两种框架都能满足我们的需求,实际的选择还是看项目场景和个人使用习惯 。

  • 相关阅读:
    spring基于xml导入配置文件
    spring中bean的继承和依赖关系
    spring整合junit
    spring新注解说明
    Web微信开发工具无法输入中文?官方bug
    vue踩坑 导出new Vue.Store首字母要大写
    关于vue ui组件
    vue组件的生命周期
    Vue的指令以及组件化开发
    webpack的npm扩展使用
  • 原文地址:https://www.cnblogs.com/markLogZhu/p/11400498.html
Copyright © 2011-2022 走看看