Spring Data

什么是Spring Data

Spring Data项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。

本文主要简述Spring Data JPA 与传统JDBC 和 Spring JDBC的优势,以及Spring Data JPA的简单使用

传统的JDBC操作数据库

使用JDBC原生操作数据库,会有很多重复性的代码,代码冗余

public class StudentDAOImpl implements StudentDAO{
    @Override
    public void save(Student student) {
        Connection connection = null;
        String sql = "insert into student(name, age) VALUES (?,?)";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtil.getConnection();  // 使用工具类获取数据库连接,减少代码冗余
            preparedStatement = connection.prepareStatement(sql);  // 获取操作数据库对象
            preparedStatement.setString(1, student.getName());  // 给sql语句的占位符赋值
            preparedStatement.setInt(2, student.getAge());
            preparedStatement.executeUpdate();  // 执行更新操作
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(resultSet, preparedStatement, connection);  // 关闭资源
        }

    }
    @Override
    public List<Student> query() {
        Connection connection = null;
        String sql = "select id, name, age from student";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<Student> listStudent = new ArrayList<>();
        try {
            connection = JDBCUtil.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();  //执行查询操作
            Student student = null;
        /*对获取的结果集执行对对象赋值(这个在JDBC中简直麻烦)
        */
            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);
                listStudent.add(student);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(resultSet, preparedStatement, connection);  // 关闭数据库资源
        }
        return listStudent;
    }
}

上述代码中虽然使用了JDBCUtil类减轻了创建连接和释放资源的过程,但获取对象还是麻烦

Spring JDBC操作数据库

在Spring 中操作数据库可以使用Spring Jdbc Template,这使得Spring 可以简化对数据库的操作,在Spring Jdbc中对原生的JDBC操作数据库做了一层简单的封装,把获取数据库连接,释放数据库资源的行为封装起来,只需在spring的配置文件中进行简单的配置,即可直接在代码中使用Spring的JdbcTemplate对数据进行增删改查即可。 JdbcTemplate在Sping配置文件中的配置:

<?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.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data"/>
    </bean>

    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!--StudentDao的装配-->
    <bean id="studentDAO" class="cc.codingape.dao.StudentDAOSpringJdbcImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

使用了JdbcTemplate的Dao实现类

/**
 * Created by TONY on 2017/8/10.
 * StudentDAO访问接口实现类:通过Spring jdbc 的方式操作
 */
public class StudentDAOSpringJdbcImpl implements StudentDAO {
    private JdbcTemplate jdbcTemplate;
    @Override
    public List<Student> query() {
        List<Student> studentList = new ArrayList<>();
        String sql = "select id, name, age from student";
        jdbcTemplate.query(sql, new RowCallbackHandler() {
            /**
             * 这个方法会循环执行resultSet里面的值,使用了回调接口获得结果集
             * 获取结果集,把结果值里面的内容赋值给具体的对象
             * @param resultSet
             * @throws SQLException
             */
            @Override
            public void processRow(ResultSet resultSet) throws SQLException {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                Student student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);
                studentList.add(student);
            }
        });
        return studentList;
    }
    @Override
    public void save(Student student) {
        // 保存学生
        String sql = "insert into student(name,age) values(?, ?)";
        jdbcTemplate.update(sql, new Object[]{student.getName(), student.getAge()});
    }
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

从上述的代码可知,Spring Jdbc Template简化了部分原生JDBC的操作

Spring Data操作数据库

使用Spring Data可以大大简化了操作数据的过程 Repository接口是Spring Data的核心接口,不提供任何方法,属于标志型接口

public interface Repository<T, ID extends Serializable> {
}

我们自定义的Repository接口需要继承Repository接口或Repository的子接口或在自定义的Repository接口上@RepositoryDefinition注解

public interface EmployeeRepository extends Repository<Employee, Integer> {
}

Repository的子接口

  • CrudRepository:继承了Repository, 实现了CRUD相关操作的方法
  • PageingAndSortingRepository:继承了CurdRepository,实现了分页排序相关的方法
  • JpaRepository:继承PagingAndSortingRepository,实现了JPA规范相关的方法

使用Spring Data 需要在Maven工程中声明相关依赖:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.5.RELEASE</version>
    </dependency>

    <!--spring data jpa-->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>1.8.0.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.3.6.Final</version>
    </dependency>

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"
       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.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data"/>
    </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="cc.codingape"/>
        <!--通过设置jpa属性,可以利用实体生成表-->
        <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="cc.codingape" entity-manager-factory-ref="entityManagerFactory"/>

    <context:component-scan base-package="cc.codingape"/>

</beans>

相关实体类:

/**
 * Created by TONY on 2017/8/11.
 * 雇员:开发实体类 ---》 自动生成数据表
 */
@Entity
@Table(name = "test_employee")  // 自定义表名
public class Employee {

    private Integer id;

    private String name;

    private Integer age;

    /**
     * 定义ID为主键和自增长
     * @return
     */
    @GeneratedValue
    @Id
    public Integer getId() {
        return id;
    }

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

    /**
     * 定义name在表中的长度为20
     * @return
     */
    @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;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

自定义的Repository接口:

public interface EmployeeRepository extends Repository<Employee, Integer> {

    /**
     * 根据约定的规则起方法名,不能乱写,否则会报错
     * @param name
     * @return
     */
    public Employee findByName(String name);

   /**
    * 这些查询方法的命名都是要按照一定规则的,缺点:麻烦,复杂sql语句难书写
    * **/

    //where name like ?% and age < ?
    public List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);

    //where name like %? and age < ?
    public List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);

    //where name in (?,?....) or age < ?
    public List<Employee> findByNameInOrAgeLessThan(List<String> nameList, Integer age);

    /**
     * 使用@Query注解,使用自定义的sql语句
     * 这种查询语句不是原生的,Employee表示的是类,不是表名,查询的内容若是全部查询,则使用类的别名例如:emp
     * **/
    @Query("select emp from Employee emp where id=(select max(id) from Employee)")
    public Employee getEmployeeByMaxId();

    /**
     * 索引形式的参数传递方式,?1表示方法中的第一个参数,
     * @param name
     * @param age
     * @return
     */
    @Query("select emp from Employee emp where emp.name=?1 and emp.age=?2")
    public List<Employee> getEmployeeByNameAndAge(String name, Integer age);

    /**
     * 参数形式的菜单传递方式
     * @param name
     * @param age
     * @return
     */
    @Query("select emp from Employee emp where emp.name=:name and emp.age=:age")
    public List<Employee> getParams2(@Param("name") String name, @Param("age") Integer age);


    @Query("select emp from Employee emp where emp.name like %?1%")
    public List<Employee> queryLike1(String name);

    /**
     * 参数传值方式,like的查询方式有些麻烦
     * @param name
     * @return
     */
    @Query("select emp from Employee  emp where emp.name like %:name%")
    public List<Employee> queryLike2(@Param("name") String name);

    /**
     * 原始的查询方式,有局限性
     * @return
     */
    @Query(nativeQuery = true, value = "select count(1) from employee")
    public long getCount();

    /**
     * 开启注解的查询语句,事务的操作需要在service层
     * @param age
     * @param name
     */
    @Modifying
    @Query("update Employee emp set emp.age=:age where emp.name=:name")
    public void updateEmployeeAge(@Param("age") Integer age, @Param("name") String name);
}

相关测试类:

public class EmpolyeeRepositoryTest {

    private ApplicationContext ctx = null;
    private EmployeeRepository employeeRepository = null;

    // 执行Test方法前自动执行初始化方法
    @Before
    public void init() {
        ctx = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeRepository = ctx.getBean(EmployeeRepository.class);
        System.out.println("init");
    }

    //执行完Test方法后执行的销毁方法
    @After
    public void destory() {
        ctx = null;
        System.out.println("destory");
    }

    @Test
    public void testFindByName(){
        Employee employee = employeeRepository.findByName("Tony");
        System.out.println(employee.toString());
    }

    @Test
    public void testFindByNameStartingWithAndAgeLessThan(){
        List<Employee> employeeList = new ArrayList<>();
        employeeList = employeeRepository.findByNameStartingWithAndAgeLessThan("test", 22);
        for (Employee employee : employeeList) {
            System.out.println(employee.toString());
        }
    }

    @Test
    public void testGetEmployeeByMaxId() {
        Employee employee = employeeRepository.getEmployeeByMaxId();
        System.out.println(employee.toString());
    }

    @Test
    public void testGetEmployeeByNameAndAge() {
        List<Employee> employeeList = new ArrayList<>();
        employeeList = employeeRepository.getEmployeeByNameAndAge("Tony", 22);
        for (Employee employee : employeeList) {
            System.out.println(employee.toString());
        }
    }

    @Test
    public void testGetParams2() {
        List<Employee> employeeList = new ArrayList<>();
        employeeList = employeeRepository.getParams2("Tony", 22);
        for (Employee employee : employeeList) {
            System.out.println(employee.toString());
        }
    }

    @Test
    public void testQueryLike1() {
        List<Employee> employeeList = new ArrayList<>();
        employeeList = employeeRepository.queryLike2("test");
        for (Employee employee : employeeList) {
            System.out.println(employee.toString());
        }
    }

    @Test
    public void testGetCount() {
        System.out.println(employeeRepository.getCount());
    }
}

在Sping Data中使用自定义自己的接口设置接口方法时有两种方法:

  1. 对于按照方法命名规则来使用的话,有弊端:

    • 方法名会比较长: 约定大于配置
    • 对于一些复杂的查询,是很难实现

    输入图片说明输入图片说明

  2. @Query注解

@Query("select emp from Employee  emp where emp.name like %:name%")
    public List<Employee> queryLike2(@Param("name") String name);

sql语句中form后面跟的是要查询的实体类而不是表名称

在Spring Data中使用事务:

    @Modifying
    @Query("update Employee emp set emp.age=:age where emp.name=:name")
    public void updateEmployeeAge(@Param("age") Integer age, @Param("name") String name);

只需在@Query上声明@Modifying注解,事务需要在Service层的相应方法声明@Transactional注解(需要在spring的配置文件中声明开启了注解)

CrudRepository接口讲解

输入图片说明

PagingAndSortingRespository接口讲解

输入图片说明

public class EmployeePagingAndSortingRespositoryTest {
    private ApplicationContext ctx = null;
    private EmployeePagingAndSortingRepository employeePagingAndSortingRespository = null;

    @Before
    public void init() {
        ctx = new ClassPathXmlApplicationContext("beans-new.xml");
        employeePagingAndSortingRespository = ctx.getBean(EmployeePagingAndSortingRepository.class);
        System.out.println("init");
    }

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

    @Test
    public void testPage() {
        //index是从0开始
        Pageable pageable = new PageRequest(0, 10);
        Page<Employee> page = employeePagingAndSortingRespository.findAll(pageable);
        System.out.println("查询的总的记录数" + page.getTotalElements());
        System.out.println("当前第几页" + page.getNumber());
        System.out.println("查询的当前页面的集合" + page.getContent());
    }

    @Test
    public void testPageAndSort() {
        Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");
        Sort sort = new Sort(order);
        //index是从0开始
        Pageable pageable = new PageRequest(0, 10, sort);
        Page<Employee> page = employeePagingAndSortingRespository.findAll(pageable);
        System.out.println("查询的总的记录数" + page.getTotalElements());
        System.out.println("当前第几页" + page.getNumber());
        System.out.println("查询的当前页面的集合" + page.getContent());
    }

}

JpaRepository接口讲解

输入图片说明

JpaSpecificationExecutor接口讲解

JpaSpecificationExecutor接口封装了JPA Criterial查询条件,在PagingAndSortingRespository接口中虽然可以进行排序等操作,但不能带查询条件。因此引入了JpaSpecificationExecutor接口

public class EmployeeSpecificationRepositoryTest {
    private ApplicationContext ctx = null;
    private EmployeeJpaSpecificationExecutorRepository employeeJpaSpecificationExecutorRepository = null;

    @Before
    public void init() {
        ctx = new ClassPathXmlApplicationContext("beans-new.xml");
        employeeJpaSpecificationExecutorRepository = ctx.getBean(EmployeeJpaSpecificationExecutorRepository.class);
        System.out.println("init");
    }

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

    /**
     * 1.分页
     * 2.排序
     * 3.查询条件 age > 50
     */
    @Test
    public void testQuery() {

        Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");
        Sort sort = new Sort(order);
        //index是从0开始
        Pageable pageable = new PageRequest(0, 10, sort);

        /**
         * root:就是我们要查询的类型
         * query:添加查询条件
         * cb:构建Predicate
         */
        Specification<Employee> specification = new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                // root (employee (age))
                Path path = root.get("age");
                return criteriaBuilder.gt(path, 50);
            }
        };

        //根据排序条件返回的page
        Page<Employee> page = employeeJpaSpecificationExecutorRepository.findAll(specification, pageable);
        System.out.println("查询的总的记录数" + page.getTotalElements());
        System.out.println("当前第几页" + page.getNumber());
        System.out.println("查询的当前页面的集合" + page.getContent());
    }

}

转载于:https://my.oschina.net/u/3592080/blog/1507522

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值