Spring Boot 实践 第五章 用Spring Data JPA进行数据访问

上一章介绍了spring boot 集成单元测试,这样spring boot 基础的部分就完成了。

这一章就聊聊在Spring boot 下用JPA 进行数据访问. Spring Data JPA 是JPA规范的一个轻量级实现. 相信大多数人在spring 时期就已经了解或者使用过Spring data jpa了.  下面先简单说下spring boot 中如何配置和使用spring data jpa.


1.先新建一个module.  在module的pom文件中引入下面这些依赖

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.28</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

2. 按照之前章节的方法构建一个spring boot的启动入口类, 代码如下: 

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.需要在yml配置文件中配置Spring data jpa和数据源信息.配置文件如下: 

   下面这是druid数据源的配置, 这个没什么说的.

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    #连接池的配置信息
    initialSize: 10
    minIdle: 10
    maxActive: 100
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20

   下面是jpa的配置 

jpa:
    show-sql: false
    generate-ddl: true
    hibernate.ddl-auto: update # Hibernate ddl auto (create, create-drop, update,none)
    database-platform: org.hibernate.dialect.MySQL5Dialect

   这些配置并不能完成spring data jpa的加载, 我们还需要分别定义druid和spring data jpa的config类

4.在写config类之前,我们先说下spring 的@Configuration和@Bean

Spring 时代,我们要写大量的xml来完成项目资源的加载. 到了Spring boot 时代, 就提倡零配置了, 其实就是换成java文件来完成配置   @Configuration 需要在类上添加, 它的作用相当于xml配置文件中的beans标签.一个配置类就相当于以前的一个配置文件. @Bean加在方法上, 相当于xml中的bean标签. 一个@Configuration标注的类,可以有多个@Bean标注的方法.

介绍完@Configuration和@Bean, 接下来先看看druid的配置类, 代码如下:

@Configuration
public class DruidDBConfig {

    @Value("${spring.datasource.url}")
    private String dbUrl;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;

    @Value("${spring.datasource.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.testOnReturn}")
    private boolean testOnReturn;

    @Value("${spring.datasource.poolPreparedStatements}")
    private boolean poolPreparedStatements;

    @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
    private int maxPoolPreparedStatementPerConnectionSize;

    @Value("{spring.datasource.connectionProperties}")
    private String connectionProperties;

    @Bean(name="dataSource") // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    public DataSource dataSource() {
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        // configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        datasource.setConnectionProperties(connectionProperties);

        return datasource;
    }
}

   注:  @Primary这个注解表示标注的bean被优先使用, 当我们配置多数据源时, 被这个标签标记的数据源优先使用.

这个类其实很好理解, 就是把通过@Value加载到yml配置的值, 注入到 dataSource这个bean的property里.  因为有@Configuration的存在, spring boot会在启动是自动加载dataSource这个bean.

5.再添加一个spring data jpa的配置类, 代码如下:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {"org.learning.repository"},
        repositoryFactoryBeanClass = JpaRepositoryFactoryBean.class)
public class RepositoryConfig {
    private static final String HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String HIBERNATE_SHOW_SQL = "hibernate.show.sql";
    private static final String HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
    private static final String HIBERNATE_EJB_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";

    @Autowired
    private DataSource dataSource;

    @Value("${spring.jpa.show-sql}")
    private String showSql;

    @Value("${spring.jpa.generate-ddl}")
    private String generateDdl;

    @Value("${spring.jpa.hibernate.ddl-auto}")
    private String hibernateDdl;

    @Value("${spring.jpa.database-platform}")
    private String databasePlatform;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setDataSource(dataSource);
        factory.setPackagesToScan("org.learning.domain");

        Map<String, Object> jpaProperties = new HashMap<>();
        jpaProperties.put(HIBERNATE_SHOW_SQL, showSql);
        jpaProperties.put(HIBERNATE_DIALECT, databasePlatform);
        jpaProperties.put(HIBERNATE_HBM2DDL_AUTO, hibernateDdl);
        jpaProperties.put(HIBERNATE_EJB_NAMING_STRATEGY, "org.hibernate.cfg.ImprovedNamingStrategy");

        factory.setJpaPropertyMap(jpaProperties);
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return txManager;
    }
}

先看看标注在类上的注解 

  • @EnableTransactionManagement  这个注解表示我们需要开启事务管理
  • @EnableJpaRepositories 这个注解表示需要开启jpa Repositories  . 这个注解中的basePackages 指定了repository存在的路径 . repositoryFactoryBeanClass 指定了创建repository实例的工厂类

接下来看看类中的两个Bean

  • entityManagerFactory  这个bean 定义了 spring data jpa 的 EntityManagerFactoryBean . 这和以前在spring 下xml定义的方式基本一样的.
  • transactionManager  这里定义了jpa的事务.

好了, 基于spring data 的数据访问层就定义好了.

6.剩下的工作就是建立domain, repository 和service了, 废话不多说, 直接上代码

一个名叫User的domain(这里使用了lombok减少代码, 如对lombok不太了解的可以看看我写的lombok介绍和配置 传送门 ):

@Data
@Entity
@Table(name="lesson_user")
public class User implements Serializable{
    /** ID */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /** 用户名 */
    @Column(length = 50)
    @NotNull
    private String userName;

    /** 密码 */
    @Column(length = 20)
    @NotNull
    private String password;

    /** 手机号 */
    @Column(length = 15)
    private String tel;

    /** 性别 */
    @Column
    @Enumerated(value = EnumType.STRING)
    private UserSex userSex;
}

再写一个 User里使用的枚举UserSex:

public enum UserSex {
    MAN, WOMAN
}

7.Domain相关的类建立好了, 这部分没什么可说的,照常写就好了. 下面写个User对应的Repository.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    /**
     * 根据用户名查找
     * @param userName 用户名
     * @return User
     */
    User findByUserName(String userName);

    /**
     * 根据手机号和用户名查找
     * @param tel 手机号
     * @param userName 用户名
     * @return User
     */
    User findByTelAndUserName(String tel, String userName);


    /**
     * 根据手机号查询用户
     * @param tel 手机号
     * @return 用户
     */
    @Query("FROM User WHERE tel=:tel")
    User findByTel(@Param("tel")String tel);

        /**
         * 分页查询
         * @param pageable
         * @return
         */
        @Query(value = "SELECT * FROM lesson_user",
            countQuery = "SELECT count(*) FROM lesson_user",
            nativeQuery = true)
        Page<User> pageAll(Pageable pageable);
}

Spring Data JPA的Repository需要注意以下几点:

  •     必须是个接口, 且需要继承JpaRepository或者JpaRepository的子类(后面章节会介绍自定义JpaRepository)
  •     继承JpaRepository后泛型必须指定对应的domain和domain的ID的类型,我们这里的ID使用的Long型.
  •     类上必须注解@Repository.
  •     Repository的方法中如果以findBy前缀, 后面跟domain的列名时,表示用这个列为查询条件. 多个列以And分割, 参数需要和查询的列保持一致
  •     可以在方法中使用@Query标注Hql进行数据库操作, 入参":"表示, 方法入参需要标记@Param. 修改和删除操作需要在@Query之外添加@Modifying标记, 表示这是对数据库进行修改操作的.
  •     想要执行分页查询可以依照例子中最后一个方法这样去写.
  •     由于当前这个例子继承了JpaRepository, 所以这个UserRepository就具有了JpaRepository的所有方法, JpaRepository的默认方法如下:
@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);
}

8.接下来, 创建Service的接口和实现, 在Service中会有使用上面JpaRepository这些默认方法的例子.代码如下:

UserService 接口:

public interface UserService {

    /**
     * 保存一个用户
     * @param user
     */
    User save(User user);

    /**
     * 根据用户名查找
     * @param userName 用户名
     * @return User
     */
    User findByUserName(String userName);

    /**
     * 根据手机号和用户名查找
     * @param tel 手机号
     * @param userName 用户名
     * @return User
     */
    User findByTelAndUserName(String tel, String userName);

    /**
     * 根据手机号查询用户
     * @param tel 手机号
     * @return 用户
     */
    User findByTel(String tel);

    /**
     * 查询列表
     * @return 一组user
     */
    List<User> list();

    /**
     * 查找列表并按照name排序
     * @return
     */
    List<User> listAndOrderByName();

    /**
     * 分页查询
     * @return
    */
    Page<User> page(Pageable pageable);

    /**
     * 清空表
     */
    void deleteAll();
}

UserServiceImpl实现:

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public User save(User user) {
        return userRepository.save(user);
    }

    @Override
    @Transactional(readOnly = true)
    public User findByUserName(String userName) {
        return userRepository.findByUserName(userName);
    }

    @Override
    @Transactional(readOnly = true)
    public User findByTelAndUserName(String tel, String userName) {
        return userRepository.findByTelAndUserName(tel, userName);
    }

    @Override
    @Transactional(readOnly = true)
    public User findByTel(String tel) {
        return userRepository.findByTel(tel);
    }

    @Override
    @Transactional(readOnly = true)
    public List<User> list() {
        return userRepository.findAll();
    }

    @Override
    @Transactional(readOnly = true)
    public List<User> listAndOrderByName() {
        return userRepository.findAll(new Sort(Sort.Direction.DESC, "id"));
    }

    @Override
    @Transactional(readOnly = true)
    public Page<User> page(Pageable pageable) {
        return userRepository.pageAll(pageable);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll() {
        userRepository.deleteAll();
    }
}

注:service实现中不要忘记添加事务@Transactional注解, 查询设置为readOnly = true

9.最后, 我们通过一个JunitTest类测试一下这些例子:

@Slf4j
public class UserServiceTest extends BaseTest {

    @Autowired
    private UserService userService;

    @Before
    public void beforeTest() {
        log.info("UserServiceTest前置方法");
        User user1 = new User();
        user1.setTel("12345678");
        user1.setUserName("测试1");
        user1.setPassword("123456");
        user1.setUserSex(UserSex.MAN);
        userService.save(user1);


        User user2 = new User();
        user2.setTel("22345678");
        user2.setUserName("测试2");
        user2.setPassword("123456");
        user2.setUserSex(UserSex.WOMAN);
        userService.save(user2);
    }

    @After
    public  void testAfterTest() {
        log.info("UserServiceTest后置方法");
        userService.deleteAll();
    }

    @Test
    public void testFindByUserName(){
        User user = userService.findByUserName("测试1");
        log.info("查询到的用户{}", JSONObject.toJSONString(user));
        Assert.notNull(user, "无效的用户名");
    }

    @Test
    public void testFindByTelAndUserName(){
        User user = userService.findByTelAndUserName("12345678", "测试1");
        log.info("查询到的用户{}", JSONObject.toJSONString(user));
        Assert.notNull(user, "无效的手机号或者用户名");
    }

    @Test
    public void testFindByTel(){
        User user = userService.findByTel("12345678");
        log.info("查询到的用户{}", JSONObject.toJSONString(user));
        Assert.notNull(user, "无效的手机号");
    }


    @Test
    public void testList(){
        List<User> userList = userService.list();
        log.info("查询到的用户{}", JSONObject.toJSONString(userList));
        Assert.notNull(userList, "空记录");
    }

        @Test
        public void testPage(){
            Page<User> page = userService.page(PageRequest.of(0,10));
            log.info("查询到的用户{}", JSONObject.toJSONString(page));
            Assert.notNull(page, "空记录");
        }

    @Test
    public void testListAndOrderByName(){
        List<User> userList = userService.listAndOrderByName();
        log.info("查询到的用户{}", JSONObject.toJSONString(userList));
        Assert.notNull(userList, "空记录");
    }
}

大家可以跑下最后的单元测试,看看是不是都能测试通过.

这一章, 介绍了Spring data jpa与spring boot的整合, 还有Spring data jpa的一些简单使用, 下一章介绍JAP在实际开发中的封装和应用.

本章结束 

下一章介绍一个封装扩展spring data jpa的例子

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值