什么是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中使用自定义自己的接口设置接口方法时有两种方法:
-
对于按照方法命名规则来使用的话,有弊端:
- 方法名会比较长: 约定大于配置
- 对于一些复杂的查询,是很难实现
-
@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());
}
}