详解Spring Data

1. Spring Data概览

主页

Spring Data的任务是为数据访问提供一个熟悉且一致的,基于Spring的编程模型,同时仍保留基础数据存储的特殊特征,这就是官网给我们的一个说明;从这里我们就可以很清楚的明白,Spring Data就是为了简化数据库的访问,例如关系型数据库MySQL,Oracle等,还有非关系型数据库Redis,MongoDB以及Elasticsearch等等,我们从它官网的主页上也不难看出,它支持很多很多的数据库;

网址:https://spring.io/projects/spring-data

当然Spring Data下面还有许多封装好的工具可以供我们使用,主要的就是下面这四个:

Spring Date JPA :减少数据访问层的开发量

Spring Date MongoDB:基于分布式数据层的数据库,在大数据层用的比较多

Spring Date Redis:开源,由C语言编写的,支持网络、内存,而且可以持久化的,提供非常多的语言支持

Spring Date Solr:高性能的基于Lucene的搜索功能,对查询性能优化,也有更好的扩展

为了能够更好的去感受Spring Data的优势,我将通过对比传统方式的代码的方式,来理解Spring Data;

代码结构目录我先截图放在这里,避免大家在看的时候,搭建工程出现问题

代码结构

2. 传统方式访问数据库

2.1 JDBC

传统的JDBC访问方式基本上就是:Connection --> Statement --> ResultSet的方式来进行访问处理

  1. 建立工程,使用IDEA,利用Maven的quickstart来快速搭建,写入依赖
<dependencies>
    <!-- MySQL依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.22</version>
    </dependency>
    <!-- junit单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13</version>
      <scope>test</scope>
    </dependency>
</dependencies>
  1. 数据库准备,我们需要去数据库准备一个简单的数据表来使用
DROP TABLE IF EXISTS student;
CREATE TABLE student(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
age INT NOT NULL,
PRIMARY KEY(id)
);

INSERT INTO student (name, age) VALUES ("zhangsan", 20);
INSERT INTO student (name, age) VALUES ("lisi", 10);
INSERT INTO student (name, age) VALUES ("wangwu", 30);
INSERT INTO student (name, age) VALUES ("zhaoliu", 40);
  1. 开发JDBCUtil工具类

    JDBCUtil工具类,放于java文件目录下

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * Created by IntelliJ IDEA.
 * 数据库连接工具类
 * 1) 数据库连接
 * 2) 关闭资源
 * @author xiren
 * @date 2021/02/15 23:11
 */
public class JdbcUtils {

    /**
     * 获取数据库连接Connection
     * @return 数据库连接
     */
    public static Connection getConnection() throws Exception {
            /**
             * 1. 定义连接参数(不建议将这部分参数写入代码区域,不便于日后的维护)
             * (a)
             * String driverClassName = "com.mysql.cj.jdbc.Driver";
             * String url = "jdbc:mysql://localhost:3306/spring_data?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai";
             * String username = "root";
             * String password = "mysql";
             */

            /**
             * (b)
             * pom.xml中使用5.x版本的mysql-connect-java依赖的,使用下方的参数
             * String driverClassName = "com.mysql.jdbc.Driver";
             * String url = "jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf8;
             * String username = "root";
             * String password = "root";
             */

            /**
             * 利用配置文件来填充参数
             * (c)
             */
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties properties = new Properties();
            properties.load(inputStream);
            String driverClassName = properties.getProperty("jdbc.driverClassName");
            String url = properties.getProperty("jdbc.url");
            String username = properties.getProperty("jdbc.username");
            String password = properties.getProperty("jdbc.password");

            /** 2. 加载驱动*/
            Class.forName(driverClassName);

            /** 3. 连接数据库*/
            Connection connection = DriverManager.getConnection(url, username, password);
            return connection;
    }

    /**
     * 关闭资源
     * @param connection 连接
     * @param preparedStatement  PreparedStatement
     */
    public static void close(Connection connection, PreparedStatement preparedStatement) {
        close(connection, preparedStatement, null);
    }

    /**
     * 关闭资源
     * @param connection 连接
     * @param preparedStatement  PreparedStatement
     * @param resultSet  ResultSet
     */
    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

配置文件db.properties,存放在resources文件夹下(配置文件当中的内容在被使用后,会从灰色变成黄色)

jdbc.driverClassName = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring_data?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
jdbc.username = root
jdbc.password = mysql

JDBCUtil工具类测试,放在test-java文件目录下

import org.junit.Assert;
import org.junit.Test;

import java.sql.Connection;

/**
 * Created by IntelliJ IDEA.
 * JDBC工具类单元测试
 * @author xiren
 * @date 2021/02/15 23:34
 */
public class JdbcUtilsTest {

    /**
     * 测试JdbcUtils工具类getConnection方法
     */
    @Test
    public void testGetConnection() throws Exception {
        Connection connection = JdbcUtils.getConnection();
        Assert.assertNotNull(connection);
    }
}
  1. 建立对象模型,DAO层开发

建立对应数据库的实体类

/**
 * Created by IntelliJ IDEA.
 * Student实体类
 * @author xiren
 * @date 2021/02/15 23:58
 */
public class Student {

    /** 主键id字段*/
    private int id;
    /** 姓名字段*/
    private String name;
    /** 年龄字段*/
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

创建DAO接口

import org.xiren.entity.Student;

import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * StudentDAO访问接口
 * @author xiren
 * @date 2021/02/16 00:03
 */
public interface StudentDAO {

    /**
     * 查询所有学生
     * @return 所有学生
     */
    List<Student> findAll();
    
     /**
     * 添加一个学生
     * @param students 待添加的学生
     */
    void save(Student students);
}

实现DAO接口的方法

import org.xiren.dao.StudentDAO;
import org.xiren.entity.Student;
import org.xiren.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * StudentDAO实现类:原始JDBC方法
 * @author xiren
 * @date 2021/02/16 00:03
 */
public class StudentDAOImpl implements StudentDAO {

    /**
     * 查询所有学生
     * @return 所有学生
     */
    public List<Student> findAll() {
        List<Student> studentList = new ArrayList<Student>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            String sql = "SELECT id, name, age from student";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            Student student = null;
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);
                studentList.add(student);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.close(connection, preparedStatement, resultSet);
        }
        return studentList;
    }
    
    /**
     * 添加一个学生
     * @param students 待添加的学生
     */
    public void save(Student students) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        String sql = "INSERT INTO student(name, age) VALUES (?, ?)";
        try {
            connection = JdbcUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,students.getName());
            preparedStatement.setInt(2,students.getAge());
            preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.close(connection, preparedStatement, resultSet);
        }
    }
}

测试类测试方法

import org.junit.Test;
import org.xiren.dao.impl.StudentDAOImpl;
import org.xiren.entity.Student;

import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * StudentDAOImpl单元测试
 * @author xiren
 * @date 2021/02/16 00:15
 */
public class StudentDAOImplTest {

    @Test
    public void testFindAll() {
        StudentDAO studentDAO = new StudentDAOImpl();
        List<Student> students = studentDAO.findAll();
        for (Student student : students) {
            System.out.println("id:" + student.getId() + ", name:" + student.getName() + ", age:" + student.getAge());
        }
    }

    @Test
    public void testSave() {
        StudentDAO studentDAO = new StudentDAOImpl();
        Student student = new Student();
        student.setName("test");
        student.setAge(50);
        studentDAO.save(student);
    }
}

这里就只写两个方法来演示一下,就这两个方法,我们就能够感觉到,其中有着大量重复性的代码以及大量重复性的操作,过于繁琐,很影响我们的开发效率,

2.2 Spring JdbcTemplate

​ 相信上面那种方式,在学习开发的过程中一定是尽力过这个繁琐的过程的,当然spring本身也有方式帮我们跟数据库进行交互,就是Spring JdbcTempla;还是同样的套路,从代码来看;

  1. 导入POM依赖

    新加入Spring-JDBC和Spring-Context两个依赖

 <dependencies>
    <!-- MySQL依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.22</version>
    </dependency>
    <!-- junit单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13</version>
      <scope>test</scope>
    </dependency>
     
    <!-- Spring JDBC依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.2</version>
    </dependency>
    <!-- Spring Context依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.2</version>
    </dependency>
  </dependencies>
  1. Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"></property>
        <property name="username" value="root"></property>
        <property name="password" value="mysql"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
	<!-- 这个是DAO的实现类写入配置文件的,在测试配置文件的时候不要引入-->
    <bean id="StudentDAO" class="org.xiren.dao.impl.StudentSpringJdbcDAOImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
</beans>

当然这里的配置好了,我们还是写一个测试来检测配置是否成功

配置文件测试类

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
 * Created by IntelliJ IDEA.
 * Spring-JDBC DataSource配置测试
 * @author xiren
 * @date 2021/02/16 18:12
 */
public class DataSourceTest {
    private ApplicationContext context = null;

    @Before
    public void setup() {
        context = new ClassPathXmlApplicationContext("beans.xml");
        System.out.println("context SET UP");
    }
    @After
    public void tearDown() {
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void TestDataSource() {
        System.out.println("DataSource");
        DataSource dataSource = (DataSource) context.getBean("dataSource");
        Assert.assertNotNull(dataSource);
    }

    @Test
    public void TestJDBCTemplate() {
        System.out.println("JdbcTemplate");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate");
        Assert.assertNotNull(jdbcTemplate);
    }
}
  1. 实现接口方法

因为我们只是来比较这两个方法,所以实现的是同一个DAO接口

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.xiren.dao.StudentDAO;
import org.xiren.entity.Student;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * StudentDAO实现类:使用Spring-JDBCTemplate方法
 * @author xiren
 * @date 2021/02/16 18:24
 */
public class StudentSpringJdbcDAOImpl implements StudentDAO {

    private JdbcTemplate jdbcTemplate;

    public List<Student> findAll() {
        final List<Student> studentList = new ArrayList<Student>();
        String sql = "SELECT id, name, age from student";
        jdbcTemplate.query(sql, new RowCallbackHandler() {
            public void processRow(ResultSet rs) throws SQLException {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                Student student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);
                studentList.add(student);
            }
        });
        return studentList;
    }

    public void save(Student students) {
        String sql = "INSERT INTO student(name, age) VALUES (?, ?)";
        jdbcTemplate.update(sql, new Object[]{students.getName(), students.getAge()});
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

测试类测试方法

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.xiren.entity.Student;

import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * StudentSpringJdbcDAOImpl单元测试
 * @author xiren
 * @date 2021/02/16 00:15
 */
public class StudentSpringJdbcDAOImplTest {

    private ApplicationContext context = null;
    private StudentDAO studentDAO = null;
    @Before
    public void setup() {
        context = new ClassPathXmlApplicationContext("beans.xml");
        studentDAO = (StudentDAO) context.getBean("StudentDAO");
        System.out.println("context SET UP");
    }
    @After
    public void tearDown() {
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void testFindAll() {
        List<Student> students = studentDAO.findAll();
        for (Student student : students) {
            System.out.println("id:" + student.getId() + ", name:" + student.getName() + ", age:" + student.getAge());
        }
    }

    @Test
    public void testSave() {
        Student student = new Student();
        student.setName("test-Spring-Jdbc");
        student.setAge(70);
        studentDAO.save(student);
    }
}
2.3 对比分析

​ 通过上面的代码,我们可以很明显的感受到,jdbcTemplate的方式,少了许多的冗余代码,无需我们再去定义一些连接等等的操作,可以更加的专注于业务层的开发;但是无论上面哪一种方法,其DAO层的代码都显得很多,且这些代码重复性很高,而且像一些常见的分页等其他的功能,也就需要我们再去做一层的封装,更加的不便于我们的实际开发,而且,代码越多,可能产生的BUG就会越多,无疑会使我们的开发更加繁琐;

3. Spring Data开发

​ 针对于上面开发的代码冗余问题,其实我们有很多的解决方案,比如说很大众的Mybatis以及它的plus版本等等可以选择的方案,这里我就使用Spring Data来进行开发了;下面的部分,为了整个对比项目的完整性,我就不在原有的一些配置文件里面进行修改,而是新创建配置文件等等;

3.1 搭建开发环境
  1. 导入pom依赖,在原有的依赖上添加jpa和hibernate依赖
	<!-- Spring Data JPA-->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>2.3.6.RELEASE</version>
    </dependency>
    <!-- hibernate  -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>5.4.28.Final</version>
    </dependency>
  1. 创建新的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!-- 1. 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"></property>
        <property name="username" value="root"></property>
        <property name="password" value="mysql"></property>
    </bean>
    <!--2. 配置EntityManagerFactory-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
        </property>
        <property name="packagesToScan" value="org.xiren"/>

        <property name="jpaProperties">
            <props>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <!-- 3. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- 4. 配置支持注解的事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 5. 配置spring data-->
    <jpa:repositories base-package="org.xiren" entity-manager-factory-ref="entityManagerFactory"/>

    <context:component-scan base-package="org.xiren"/>

</beans>
  1. 配置文件测试

新创建一个实体类,来测试配置文件里面的自动生成表的功能即可测试配置文件是否生效

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

/**
 * Created by IntelliJ IDEA.
 * 员工实体类,用于对Spring Data的配置文件自动生成功能测试以验证配置文件生效
 * 顺序:先写好实体类,然后再去自动建表
 * @author xiren
 * @date 2021/02/17 11:58
 */
@Entity
public class Employee {

    /** 主键id*/
    private Integer id;
    /** 姓名*/
    private String name;
    /** 年龄*/
    private Integer age;

    @GeneratedValue
    @Id
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(length = 20)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

单元测试

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by IntelliJ IDEA.
 * Spring Data配置文件测试
 * @author xiren
 * @date 2021/02/17 11:55
 */
public class SpringDataSourceTest {
    private ApplicationContext ctx = null;

    @Before
    public void setup(){
        ctx = new ClassPathXmlApplicationContext("beans-new.xml");
        System.out.println("setup");
    }

    @After
    public void tearDown(){
        ctx = null;
        System.out.println("tearDown");
    }

    /**
     * 配置EntityManagerFactory测试
     */
    @Test
    public void testEntityManagerFactory(){

    }
}
3.2 接口以及测试类

定义接口

import org.springframework.data.repository.Repository;
import org.xiren.entity.Employee;

/**
 * Created by IntelliJ IDEA.
 *
 * @author xiren
 * @date 2021/02/17 13:47
 */
public interface EmployeeRepository extends Repository<Employee, Integer> {

    /**
     * 通过名字查找雇员
     * @param name 名字
     * @return 雇员信息
     */
    Employee findByName(String name);
}

接口测试类,其中数据我们自行插入

INSERT INTO employee (name, age) VALUES ("zhangsan", 20);
INSERT INTO employee (name, age) VALUES ("lisi", 10);
INSERT INTO employee (name, age) VALUES ("wangwu", 30);
INSERT INTO employee (name, age) VALUES ("zhaoliu", 40);
INSERT INTO employee (name, age) VALUES ("test1", 90);
INSERT INTO employee (name, age) VALUES ("test2", 80);
INSERT INTO employee (name, age) VALUES ("test3", 70);
INSERT INTO employee (name, age) VALUES ("test4", 60);
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.xiren.entity.Employee;

/**
 * Created by IntelliJ IDEA.
 * EmployeeRepository接口测试类
 * @author xiren
 * @date 2021/02/17 13:52
 */
public class EmployeeRepositoryTest {
    private ApplicationContext context = null;
    private EmployeeRepository employeeRepository = null;
    @Before
    public void setup(){
        context = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeRepository = context.getBean(EmployeeRepository.class);
        System.out.println("context SET UP");
    }

    @After
    public void tearDown(){
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void TestFindByName() {
        // System.out.println(employeeRepository);
        Employee employee = employeeRepository.findByName("zhangsan");
        System.out.println("employee's id : " + employee.getId());
    }
}

​ 写到这里,就应该有一个主观的感受了,我们使用了Spring Data之后,仅仅只是定义了一个接口,甚至没有写它的实现类,就完成了我们想要进行的数据查询功能,单从效果上面来看同传统的方式没有任何的区别,但是实际上呢,减少了许许多多行的代码;瞬间大大降低了开发的繁琐程度;

​ 在这里面,就不得不提一下我们接口类里面继承的Repository这个类了,因为有了它,我们才快速实现了我们的功能;

4. Spring Data JPA

4.1 Repository接口
  1. Repository接口是Spring Data的核心接口,不提供任何方法
public interface Repository<T, ID> {}
/**
 * 1) Repository接口,是一个标记接口(没有包含方法声明的接口)
 * 2)如果我们定义的接口EmployeeRepository extends Repository<Employee, Integer>,就说明该接口会被Spring所管理
 * 3) 你也可以在测试类里面打开注释去打印一下这个接口,结果为org.springframework.data.jpa.repository.support.SimpleJpaRepository@24386839
 * 4) 如果方法没有继承Repository接口,再来执行测试类的话,就会如下报错
 * org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.xiren.repository.EmployeeRepository' available
 * 这个报错就是并没有一个可用的bean,也就是当前接口没有被纳入Spring进行管理,当然也可以如下解决这个问题
 * 在接口类中添加如下注解
 *  @RepositoryDefinition(domainClass = Employee.class, idClass = Integer.class)
 *  public interface EmployeeRepository{}
 */

  1. Repository的子接口

a. CrudRepository:继承Repository,实现类CRUD相关的方法;

b. PagingAndSortingRepository:继承CrudRepository接口,实现了分页排序的相关方法;

c. JpaRepository:继承PagingAndSortingRepository接口,实现类JPA规范相关的方法;

4.2 Repository中查询方法定义规则和使用
  1. 了解Spring Data中查询方法名称的定义规则

    KeyWordSampleJPQL snippet
    AndfindByLastnameAndFirstname…where x.lastname = ?1 and x.firstname = ?2
    OrfindByLastnameOrFirstname…where x.lastname = ?1 or x.firstname = ?2
    BetweenfindByStartDateBetween…where x.startDate between ?1 and ?2
    Less ThanfindByAgeLessThan…where x.age < ?1
    Greater ThanfindByAgeGreaterThan…where x.age > ?1
    AfterfindByStartDateAfter…where x.startDate > ?1
    BeforefindByStartDateBefore…where x.startDate > ?1
    IsNullfindByAgeIsNull…where x.age is null
    IsNotNULL,NotNullfindByAge(Is)NotNull…where x.age not null

    下面就来写个Demo来示范一下上面的定义规则:还是拿employee这个表,做一个名字的模糊查询和年龄大小的混合查询

    方法接口

    import org.springframework.data.repository.Repository;
    import org.xiren.entity.Employee;
    
    import java.util.List;
    
    /**
     * Created by IntelliJ IDEA.
     *
     * @author xiren
     * @date 2021/02/17 13:47
     */
    
    public interface EmployeeRepository extends Repository<Employee, Integer> {
    
        /**
         * 通过名字查找雇员
         * @param name 名字
         * @return 雇员信息
         */
        Employee findByName(String name);
    
        /**
         * 查询名字以指定值开头且年龄小于指定值
         * @param name 指定的名字开头
         * @param age 指定年龄大小
         * @return 雇员信息
         */
        List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);
    
        /**
         * 查询名字以指定值结束且年龄小于指定值
         * @param name 指定的名字开头
         * @param age 指定年龄大小
         * @return 雇员信息
         */
        List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);
    
        /**
         * 查询名字在指定范围内或年龄小于指定值
         * @param names 名字指定范围
         * @param age 指定年龄大小
         * @return 雇员信息
         */
        List<Employee> findByNameInOrAgeLessThan(List<String> names, Integer age);
    
        /**
         * 查询名字在指定范围内且年龄小于指定值
         * @param names 名字指定范围
         * @param age 指定年龄大小
         * @return 雇员信息
         */
        List<Employee> findByNameInAndAgeLessThan(List<String> names, Integer age);
    }
    

    接口测试类

    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.xiren.entity.Employee;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by IntelliJ IDEA.
     * EmployeeRepository接口测试类
     * @author xiren
     * @date 2021/02/17 13:52
     */
    public class EmployeeRepositoryTest {
        private ApplicationContext context = null;
        private EmployeeRepository employeeRepository = null;
        @Before
        public void setup(){
            context = new ClassPathXmlApplicationContext("beans-new.xml");
            employeeRepository = context.getBean(EmployeeRepository.class);
            System.out.println("context SET UP");
        }
    
        @After
        public void tearDown(){
            context = null;
            System.out.println("context TEAR DOWN");
        }
    
        @Test
        public void TestFindByName() {
    //        System.out.println(employeeRepository);
    //        org.springframework.data.jpa.repository.support.SimpleJpaRepository@24386839
            Employee employee = employeeRepository.findByName("zhangsan");
            System.out.println("employee's id : " + employee.getId());
        }
    
        @Test
        public void TestFindByNameStartingWithAndAgeLessThan() {
            List<Employee> employees = employeeRepository.findByNameStartingWithAndAgeLessThan("test", 80);
            for (Employee employee : employees) {
                System.out.println("employee's id : " + employee.getId());
            }
        }
    
        @Test
        public void TestFindByNameEndingWithAndAgeLessThan() {
            List<Employee> employees = employeeRepository.findByNameEndingWithAndAgeLessThan("u", 80);
            for (Employee employee : employees) {
                System.out.println("employee's id : " + employee.getId());
            }
        }
    
        @Test
        public void TestFindByNameInOrAgeLessThan() {
            List<String> names = new ArrayList<String>();
            names.add("test1");
            names.add("zhangsan");
            List<Employee> employees = employeeRepository.findByNameInOrAgeLessThan(names, 60);
            for (Employee employee : employees) {
                System.out.println("employee's id : " + employee.getId());
            }
        }
    
        @Test
        public void TestFindByNameInAndAgeLessThan() {
            List<String> names = new ArrayList<String>();
            names.add("test1");
            names.add("zhangsan");
            List<Employee> employees = employeeRepository.findByNameInAndAgeLessThan(names, 60);
            for (Employee employee : employees) {
                System.out.println("employee's id : " + employee.getId());
            }
        }
    }
    

    写好了这些Demo后,确实,一些组合型的查询也能够解决,但是这个方法名确实是有些许的长,使用起来感觉没有那么方便,并且对于一些更加复杂的查询,是很难实现的;为了解决这个问题,下面的部分,我们将引入Query注解,来处理目前的一些问题;

4.3 使用Spring Data完成复杂查询方法名称的命名
  1. Query注解的使用

a. 在Respository方法中使用,不需要遵循查询方法命名规则;

b. 只需要将@Query定义在Respository中的方法之上即可;

c. 命名参数以及索引参数的使用,且支持本地查询;

  1. Modifying注解的使用以及结合Query直接执行更新操作
  2. Transactional注解在SpringData中的使用

接口方法

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import org.xiren.entity.Employee;

import java.util.List;

/**
 * Created by IntelliJ IDEA.
 *
 * @author xiren
 * @date 2021/02/17 13:47
 */

public interface EmployeeRepository extends Repository<Employee, Integer> {

    /**
     * 通过名字查找雇员
     * @param name 名字
     * @return 雇员信息
     */
    Employee findByName(String name);

    /**
     * 查询名字以指定值开头且年龄小于指定值
     * @param name 指定的名字开头
     * @param age 指定年龄大小
     * @return 雇员信息
     */
    List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);

    /**
     * 查询名字以指定值结束且年龄小于指定值
     * @param name 指定的名字开头
     * @param age 指定年龄大小
     * @return 雇员信息
     */
    List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);

    /**
     * 查询名字在指定范围内或年龄小于指定值
     * @param names 名字指定范围
     * @param age 指定年龄大小
     * @return 雇员信息
     */
    List<Employee> findByNameInOrAgeLessThan(List<String> names, Integer age);

    /**
     * 查询名字在指定范围内且年龄小于指定值
     * @param names 名字指定范围
     * @param age 指定年龄大小
     * @return 雇员信息
     */
    List<Employee> findByNameInAndAgeLessThan(List<String> names, Integer age);

    /**
     * 查询最大id员工
     * @return 雇员信息
     */
    @Query("select o from Employee o where id = (select max(id) from Employee t1)")
    Employee getEmployeeByMaxId();

    /**
     * 根据姓名和年龄查询
     * @param name 指定名字
     * @param age 指定年龄
     * @return 雇员信息
     */
    @Query("select o from Employee o where o.name = ?1 and o.age = ?2")
    List<Employee> queryParams1(String name, Integer age);

    /**
     * 根据姓名和年龄查询
     * @param name 指定名字
     * @param age 指定年龄
     * @return 雇员信息
     */
    @Query("select o from Employee o where o.name = :name and o.age = :age")
    List<Employee> queryParams2(@Param("name") String name, @Param("age") Integer age);

    /**
     * 根据名字模糊查询
     * @param name 指定名字字段
     * @return 雇员信息
     */
    @Query("select o from Employee o where o.name like %?1%")
    List<Employee> queryLike1(String name);

    /**
     * 根据名字模糊查询
     * @param name 指定名字字段
     * @return 雇员信息
     */
    @Query("select o from Employee o where o.name like %:name%")
    List<Employee> queryLike2(@Param("name") String name);

    /**
     * 统计所有员工数量
     * @return 统计值
     */
    @Query(nativeQuery = true, value = "select count(1) from employee")
    long getCount();

    /**
     * 根据id更新员工年龄
     * @param id 指定id
     * @param age 需要更新的年龄
     */
    @Modifying
    @Query("update Employee o set o.age = :age where o.id = :id")
    void update(@Param("id") Integer id, @Param("age") Integer age);
}

接口方法测试类

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.xiren.entity.Employee;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * EmployeeRepository接口测试类
 * @author xiren
 * @date 2021/02/17 13:52
 */
public class EmployeeRepositoryTest {
    private ApplicationContext context = null;
    private EmployeeRepository employeeRepository = null;
    @Before
    public void setup(){
        context = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeRepository = context.getBean(EmployeeRepository.class);
        System.out.println("context SET UP");
    }

    @After
    public void tearDown(){
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void TestFindByName() {
//        System.out.println(employeeRepository);
//        org.springframework.data.jpa.repository.support.SimpleJpaRepository@24386839
        Employee employee = employeeRepository.findByName("zhangsan");
        System.out.println("employee's id : " + employee.getId());
    }

    @Test
    public void TestFindByNameStartingWithAndAgeLessThan() {
        List<Employee> employees = employeeRepository.findByNameStartingWithAndAgeLessThan("test", 80);
        for (Employee employee : employees) {
            System.out.println("employee's id : " + employee.getId());
        }
    }

    @Test
    public void TestFindByNameEndingWithAndAgeLessThan() {
        List<Employee> employees = employeeRepository.findByNameEndingWithAndAgeLessThan("u", 80);
        for (Employee employee : employees) {
            System.out.println("employee's id : " + employee.getId());
        }
    }

    @Test
    public void TestFindByNameInOrAgeLessThan() {
        List<String> names = new ArrayList<String>();
        names.add("test1");
        names.add("zhangsan");
        List<Employee> employees = employeeRepository.findByNameInOrAgeLessThan(names, 60);
        for (Employee employee : employees) {
            System.out.println("employee's id : " + employee.getId());
        }
    }

    @Test
    public void TestFindByNameInAndAgeLessThan() {
        List<String> names = new ArrayList<String>();
        names.add("test1");
        names.add("zhangsan");
        List<Employee> employees = employeeRepository.findByNameInAndAgeLessThan(names, 60);
        for (Employee employee : employees) {
            System.out.println("employee's id : " + employee.getId());
        }
    }

    @Test
    public void testGetEmployeeByMaxId() {
        Employee employee = employeeRepository.getEmployeeByMaxId();
        System.out.println("id:" + employee.getId()
                + " , name:" + employee.getName()
                + " ,age:" + employee.getAge());
    }

    @Test
    public void testQueryParams1() {
        List<Employee> employees = employeeRepository.queryParams1("zhangsan", 20);
        for (Employee employee : employees) {
            System.out.println("id:" + employee.getId()
                    + " , name:" + employee.getName()
                    + " ,age:" + employee.getAge());
        }
    }

    @Test
    public void testQueryParams2() {
        List<Employee> employees = employeeRepository.queryParams2("zhangsan", 20);
        for (Employee employee : employees) {
            System.out.println("id:" + employee.getId()
                    + " , name:" + employee.getName()
                    + " ,age:" + employee.getAge());
        }
    }


    @Test
    public void testQueryLike1() {
        List<Employee> employees = employeeRepository.queryLike1("test");
        for (Employee employee : employees) {
            System.out.println("id:" + employee.getId()
                    + " , name:" + employee.getName()
                    + " ,age:" + employee.getAge());
        }
    }

    @Test
    public void testQueryLike2() {
        List<Employee> employees = employeeRepository.queryLike2("test1");
        for (Employee employee : employees) {
            System.out.println("id:" + employee.getId()
                    + " , name:" + employee.getName()
                    + " ,age:" + employee.getAge());
        }
    }

    @Test
    public void testGetCount() {
        long count = employeeRepository.getCount();
        System.out.println("count:" + count);
    }
}

这里面设计到事务操作的更新操作的代码,我们还是按照习惯,写在了一个service包当中;

service服务层

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.xiren.repository.EmployeeRepository;

import javax.transaction.Transactional;

/**
 * Created by IntelliJ IDEA.
 * 事务操作,更新功能service实现类
 * @author xiren
 * @date 2021/02/17 19:39
 */
@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    /**
     * 根据id更新员工年龄
     * @param id 指定id
     * @param age 需要更新的年龄
     */
    @Transactional
    public void update(Integer id, Integer age) {
        employeeRepository.update(id, age);
    }
}

service业务测试类

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by IntelliJ IDEA.
 * 更新功能service实现测试类
 * @author xiren
 * @date 2021/02/17 19:40
 */
public class EmployeeServiceTest {

    private ApplicationContext ctx = null;
    private EmployeeService employeeService = null;

    @Before
    public void setup() {
        ctx = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeService = ctx.getBean(EmployeeService.class);
        System.out.println("setup");
    }

    @After
    public void tearDown() {
        ctx = null;
        System.out.println("tearDown");
    }

    @Test
    public void testUpdate() {
        employeeService.update(1, 55);
    }
}

上面的Demo就很清楚看到了更加便捷的增删改查方法,总结一下就是:事务一般在service层中来实现;@Query注解@Modifying@Transactional这些的注解实现了更加简便的事务代码开发,这里删除的代码就不在做演示了,有兴趣的可以自己去试一下;

4.4 CrudRepository接口的使用

CrudRepository:继承Repository,实现类CRUD相关的方法;其中包含了以下等许多的方法

save(entity)saveAll(entities)findOne(id)
findAll()delete(id)existe(id)
delete(entity)delete(entities)deleteAll()

使用的方法也很简单,就跟我们平时所写的CURD基本上差不多,只是需要去继承CrudRepository,大大的减少了代码量;

public interface EmployeeCrudRepository extends CrudRepository<Employee, Integer> {}

还是来写个Demo来演示一下

Service业务层

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.xiren.entity.Employee;
import org.xiren.repository.EmployeeCrudRepository;
import org.xiren.repository.EmployeeRepository;

import javax.transaction.Transactional;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * 事务操作,更新功能service实现类
 * @author xiren
 * @date 2021/02/17 19:39
 */
@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private EmployeeCrudRepository employeeCrudRepository;

    /**
     * 根据id更新员工年龄
     * @param id 指定id
     * @param age 需要更新的年龄
     */
    @Transactional
    public void update(Integer id, Integer age) {
        employeeRepository.update(id, age);
    }

    /**
     * 保存多条员工信息
     * @param employees 多条员工信息
     */
    @Transactional
    public void save(List<Employee> employees) {
        employeeCrudRepository.saveAll(employees);
    }
}

测试类

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.xiren.entity.Employee;
import org.xiren.service.EmployeeService;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * EmployeeCrudRepository接口测试类
 * @author xiren
 * @date 2021/02/17 13:52
 */
public class EmployeeCrudRepositoryTest {
    private ApplicationContext context = null;
    private EmployeeService employeeService = null;
    @Before
    public void setup(){
        context = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeService = context.getBean(EmployeeService.class);
        System.out.println("context SET UP");
    }

    @After
    public void tearDown(){
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void testSave() {
        List<Employee> employees = new ArrayList<Employee>();
        Employee employee = null;
        for (int i = 0; i < 10; i++) {
            employee = new Employee();
            employee.setName("demo" + i);
            employee.setAge(1000 - i);
            employees.add(employee);
        }
        employeeService.save(employees);
    }
}
4.5 PagingAndSortingRepository接口的使用

该接口继承CrudRepository接口,实现了分页排序的相关方法;带排序的查询findAll(Sort sort);带排序的分页查询findAll(Pageable pageable);

public interface EmployeePagingAndSortingRepository extends PagingAndSortingRepository<Employee, Integer> {}

测试类

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.xiren.entity.Employee;


/**
 * Created by IntelliJ IDEA.
 * EmployeeRepository接口测试类
 * @author xiren
 * @date 2021/02/17 13:52
 */
public class EmployeePagingAndSortingRepositoryTest {
    private ApplicationContext context = null;
    private EmployeePagingAndSortingRepository employeePagingAndSortingRepository = null;
    @Before
    public void setup(){
        context = new ClassPathXmlApplicationContext("beans-new.xml");
        employeePagingAndSortingRepository = context.getBean(EmployeePagingAndSortingRepository.class);
        System.out.println("context SET UP");
    }

    @After
    public void tearDown(){
        context = null;
        System.out.println("context TEAR DOWN");
    }

    @Test
    public void testPage() {
        Pageable pageable = PageRequest.of(1,4);
        Page<Employee> page = employeePagingAndSortingRepository.findAll(pageable);
        System.out.println("总页数" + page.getTotalElements());
        System.out.println("总记录数" + page.getTotalElements());
        System.out.println("当前是:" + page.getNumber()+1);
        System.out.println("当前页面的集合" + page.getContent());
        System.out.println("当前页面的记录时" + page.getNumberOfElements());
    }
}

手写过分页的都知道自己去写一个分页的那个代码量和痛苦,这里就很好的减轻了我们的代码量,提高了效率;

JpaRepository这个接口的使用和上面的接口使用大同小异,这里也就不过多概述了

现在能够实现上述功能的框架也有很多,所以这里SpringData就写的没有那么详细,希望能够对大家有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值