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

本文详细介绍SpringBoot环境下集成SpringData JPA进行数据访问的方法,包括配置、使用及示例代码,适合初学者快速上手。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一章介绍了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的例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值