转载

spring boot 整合 jpa

spring boot 整合 jpa

在这里简单的介绍下jpa,jpa作为Hibernate的一个分支,理所应当和Hibernate有异曲同工之妙。与我们大部分使用的mybatis不同的是他不需要你写任务繁多的大大小小的sql语句(当然了是jpa原生的方法满足不了的情况下)。但是在大型项目中,jpa和Hibernate的缺点就显露出来,sql难度越大关联的表越多,对于jpa和Hibernate来说,很难实现。但是优点就是他在小型项目中并不需要重复编写相同的增删改查语句,大大节省了工作量。

jpa的核心

jpa抽象的中央接口的核心是Repository,一般只做原生实现所用,在项目中用的最多的是他的子类,JpaRepository、CrudRepository、PageableRepository。下面我们通过一边整合一边熟悉jpa。

整合Jpa

pom包

我们先导入所需的jar包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>accessingdatawithjpa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>accessingDataWithJpa</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<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>
		<!-- jpa -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<version>2.1.2.RELEASE</version>
		</dependency>
		<!-- 日志 -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>

		<!-- spring web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- MYSQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!-- Alibaba连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.25</version>
		</dependency>

		<!-- json -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.58</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.8</version>
			<scope>provided</scope>
		</dependency>


	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
复制代码

导入的包不用多少,这边用的mysql。

jpa的实体类

这里我们先创建一个model类。

package com.example.demo.model;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;


/**
 * Created by 神烦 on 2019/8/27.
 */
@Entity //jpa标注实体
@Table(name = "t_customer") // 创建/修改表的名称
@Data
public class Customer {

    @Id // 主键
    //@GeneratedValue(strategy = GenerationType.AUTO)
    //主键自增 Auto为默认使用oracle自增的方式 所以在运行时会多生成一张表 记录从1开始 记录主键
    //这里不适用与mysql 但是使用@GeneratedValue(strategy = GenerationType.IDENTITY)会报主键不能为空错误
    private Long id;

    @Column(name = "name", nullable = true) // 数据库对应字段名 非空约束(可以为空)
    private String name;

    @Column(name = "remark", nullable = true)// 数据库对应字段名 非空约束(可以为空)
    private String remark;

    public Customer() {
    }

    public Customer(String name, String remark) {
        this.name = name;
        this.remark = remark;
    }

    public Customer(Long id, String name, String remark) {
        this.id = id;
        this.name = name;
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '/'' +
                ", remark='" + remark + '/'' +
                '}';
    }
}
复制代码

这里的实体类要了解一下他的注解,每个jpa相关的实体类的标识标签为@Entity,@Table(value="数据表名"),@Id主键标识,在这里有个坑,当设置主键为适合mysql数据库的@GeneratedValue(strategy = GenerationType.IDENTITY)时,在插入数据时,会爆出主键为空的异常。还有就是导包时小心别倒错包,我就因为倒错了包折腾了不少时间。包的路径为:import javax.persistence.*

jpa repository数据持久层

所以的自动化操作都是有该层操作,下面我们先来创建一个。

package com.example.demo.dao;

import com.example.demo.model.Customer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.lang.Nullable;

import java.util.List;


/**
 * Created by 神烦 on 2019/8/27.
 */
// @NoRepositoryBean 按照实际项目使用 作用为隐藏父类方法 只提供当前类所有的方法
public interface ICustomerDao extends JpaRepository<Customer, Long>{

    // 根据指定参数查询 查询 !findBy+字段+对应查询词!
    @Nullable // 接受返回值为空 接受参数为空 相反不为空使用@NonNull
    List<Customer> findByNameAndRemark(@Nullable String name, String remark);

    // query自定义查询 占位符后面要跟对应位数的数字 否则会报错
    @Query(value = "select * from t_customer where remark = ?1",
    countQuery = "select count(*) from t_customer where remark = ?1",
    nativeQuery = true) //nativeQuery可以使用原生sql
    Page<Customer> findByRemark(String remark, Pageable pageable);
}
复制代码

在常用的三种repository中,这次使用的是JpaRepository,满足基本的增删改查的操作。这里的几个标签也要讲解一下。@NoRepositoryBean多用于继承repository接口中隐藏被继承者的上一级的方法。@Nullable用于接受返回值和参数的null值,相反@NonNull不接受为空。@Query用于自定义sql语句的编写。这里的方法命名也尤为重要,常规的查询操作都是read..by..,find..by..诸如此类,所有有兴趣的朋友可以去了解一下。

jpa config

和持久层一样配置,注入数据源,设置好工厂类即可。我们先来配置数据源再配置jpa,配置可能稍许繁琐。

  • application.yml
# 服务器地址
serverIp: 114.80.222.219
databasePort: 3306
databaseName: test

# 数据源基础配置
spring:
  datasource:
    type: com.mysql.jdbc.Driver
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://${serverIp}:${databasePort}/${databaseName}?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull
    username: root
    password:


    # 连接池配置
    # 初始化大小,最小,最大
    initialSize: 1
    minIdle: 1
    maxActive: 50
    # 配置获取连接等待超时的时间
    maxWait: 180000

    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000

    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false

    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    filters: stat,wall
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000


jpa:
  hibernate:
    ddl-auto: update
  show-sql: true
复制代码
  • druidConfig
package com.example.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
* Created by 神烦 on 2019/9/2.
*/
@Component
@ConfigurationProperties(prefix="spring.datasource")
public class DruidConfig {

   private static Logger logger = LoggerFactory.getLogger(DruidConfig.class);

   @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.filters}")
   private String filters;

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

   @Bean     //声明其为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);
       try {
           datasource.setFilters(filters);
       } catch (SQLException e) {
           logger.error("druid configuration initialization filter", e);
       }
       datasource.setConnectionProperties(connectionProperties);

       return datasource;
   }
}
复制代码
  • jpaCongfig
package com.example.demo.config;

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

/**
 * Created by 神烦 on 2019/8/27.
 */
@Configuration // 标识配置类
@EnableJpaRepositories(basePackages = "com.example.demo.dao")  // 指定持久层地址
@EnableTransactionManagement
public class JpaConfiguration {

    @Resource
    private DataSource dataSource; // 注入数据源

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.example.demo.model"); // 指定实体类位置
        factory.setDataSource(dataSource);
        factory.setPersistenceUnitName("default");   // 工厂名
        factory.afterPropertiesSet();
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}
复制代码

这里需要注意的是要指定好jpa的model和dao层扫描的位置,这边也是很容易报错。这边需要你更换一下你自己的数据库了。

测试

package com.example.demo.controller;

import com.example.demo.dao.ICustomerDao;
import com.example.demo.model.Customer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;


/**
 * Created by 神烦 on 2019/8/27.
 */
@Controller
public class CustomerController {
    private static Logger logger = LoggerFactory.getLogger(CustomerController.class);

    @Autowired
    private ICustomerDao repository;

    /**
     * 测试jpa查询方法
     * JpaRository查询
     * Jpa名称规范查询
     * 分页查询
     * 自定义查询语句查询
     * @return
     */
    @RequestMapping("/api/findCustomer.json")
    @ResponseBody
    public Object findCustomer() {
        // 简单插入数据
        repository.save(new Customer(999L, "test", "888"));
        logger.info("查看customer是否插入成功 :{}", repository.findById(999L).toString());
        logger.info("-----------------------------------------------------------------");

        // 删除数据
        repository.deleteById(999L);
        List<Customer> customers = repository.findAll();
        logger.info("查看删除是否成功 :{}", customers.size());
        logger.info("-----------------------------------------------------------------");

        // 名称规范简单查询
        repository.save(new Customer(999L, "test", "888"));
        logger.info("查询名称规范简单查询结果 :{}", repository.findByNameAndRemark("test", "888").size());
        logger.info("-----------------------------------------------------------------");

        // 自定义sql语句查询
        Sort sort = new Sort(Sort.Direction.DESC, "id"); // 排序顺序 排序字段
        Pageable pageable = PageRequest.of(1,20,sort);
        Page<Customer> customerPage = repository.findByRemark("888", pageable);
        logger.info("自定义分页查询结果...... :{}", customerPage.getTotalPages());
        // 分页查询
        return "<p1>ok</p1>";
        }

}
复制代码

这边提供了几种常用的sql语句测试用例,有兴趣的朋友可以拿过来跑一跑。

测试结果

2019-09-03 19:49:28.210  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : 查看customer是否插入成功 :Optional[Customer{id=999, name='test', remark='888'}]
2019-09-03 19:49:28.210  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : -----------------------------------------------------------------
2019-09-03 19:49:28.264  INFO 3116 --- [nio-8080-exec-2] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
2019-09-03 19:49:28.379  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : 查看删除是否成功 :0
2019-09-03 19:49:28.379  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : -----------------------------------------------------------------
2019-09-03 19:49:28.459  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : 查询名称规范简单查询结果 :1
2019-09-03 19:49:28.459  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : -----------------------------------------------------------------
2019-09-03 19:49:28.510  INFO 3116 --- [nio-8080-exec-2] c.e.demo.controller.CustomerController   : 自定义分页查询结果...... :1
复制代码

至此spring boot整合jpa就成功了,整合过程中,在设置dao层路径和model的导包两个地方是个坑,望众知。源码注释很全,有需要的可以下载源码体验一下。

源码地址 : github.com/Liyinzuo/ac…

有不足或者错误的地方欢迎指正,谢谢~

原文  https://juejin.im/post/5d6e3f35e51d45620064bba3
正文到此结束
Loading...