最近看了这个教程学习SpringData技术。老师开头演示了传统的JDBC使用方法,非常的繁琐,需要在逻辑代码中进行配置。每一次操作数据库都要通过建立连接、执行查询、释放资源三部重复的代码,还要写一个JDBCUtil工具类来整合数据库连接和关闭的逻辑:
/**
* JDBC工具类:
* 1) 获取Connection
* 2) 释放资源
*/
public class JDBCUtil {
/**
* 获取Connection
* @return 所获得到的JDBC的Connection
*/
public static Connection getConnection() throws Exception {
/**
* 不建议大家把配置硬编码到代码中
*
* 最佳实践:配置性的建议写到配置文件中
*/
// String url = "jdbc:mysql:///spring_data";
// String user = "root";
// String password = "root";
// String driverClass = "com.mysql.jdbc.Driver";
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
String url = properties.getProperty("jdbc.url");
String user = properties.getProperty("jdbc.user");
String password = properties.getProperty("jdbc.password");
String driverClass = properties.getProperty("jdbc.driverClass");
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 释放DB相关的资源
* @param resultSet
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet,
Statement statement, Connection connection){
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
为了避免这样繁琐的配置操作,Spring框架内置的 JdbcTemplate模板来了,它将数据的配置独立到了.xml
文件中,和逻辑代码独立开来,很大程度简化了代码,助力开发:
- 添加Maven依赖(搜索Maven Repository,在Maven仓库中找到需要的依赖)
这里需要添加两个依赖:spring-jdbc&spring-context? - 配置beans.xml:DataSource&JdbcTemplate
- 开发spring jdbc版本的query和save方法
- 写Test Case
在Spring中,我们将beans.xml
配置文件放在Resources
文件夹下,这里截取部分主要内容:
<?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="root"/>
<property name="url" value="jdbc:mysql:///spring_data"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="studentDAO" class="com.imooc.dao.StudentDAOSpringJdbcImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
上面是视频中的代码,由于各种更新,我在《精通Spring 4.x》中学习的下面的代码也是可行的,原理逻辑是一样的,自己对比:
<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.smart.dao"/>
<context:component-scan base-package="com.smart.service"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/sampledb"
p:username="root"
p:password="123456"/>
<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
总结弊端
- DAO has many many code
- DAOImpl has many duplicate code
- Develop the page and other functions对于开发分页和开发其他方法工作量很大
Spring Data JPA
PS:这部分的总结参考这里
SpringData JPA只是SpringData中的一个子模块
JPA是一套标准接口,而Hibernate是JPA的实现
SpringData JPA 底层默认实现是使用Hibernate
(Hibernate是什么呢?Hibernate是一种Java语言下的对象关系映射(ORM)解决方案。是一种ORM框架,全称为 Object_Relative DateBase-Mapping,在Java对象与关系数据库之间建立某种映射,以实现直接存取Java对象!通俗点说,就是Hibernate能将一个领域对象绑定到数据库的一张表上,无论你是想新建表还是CRUD这张表的数据,你只需要对这个类进行操作就可以了,而不需要直接去操作数据库,教程中的实例就是雇员: 先开发实体类===>自动生成对应的数据表)
开发环境搭建
首先我们要写配置文件,也就是POM和resources
里面的.xml
文件。这部分我踩了个坑,我根据教程写好POM配置以后,并没有办法测试SpringDataTest
。报错信息第一句是:
java.sql.SQLException: Unknown system variable 'query_cache_size'
感谢isxuran给我提供的解决方案,原因是mysql-connecter-java
的版本过低,很显然是数据库驱动程序与数据库版本不对应。所以我修改了mysql-connector-java
的依赖版本,由教程中的5.1.38
改成了5.1.6
。为什么选择5.1.6
呢,我只是在maven repository
里网上找了一个使用率较高的进行尝试,没有报错,问题解决。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ting</groupId>
<artifactId>springdataPratice</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
<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>
</dependencies>
</project>
beans.xml
中主要配置了数据源dataSource
、Hibernate会用到的EntityManagerFactory
。
<?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="123456"/>
<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="com.ting"/>
<property name="jpaProperties">
<props>
<!--命名策略-->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!--使用的方言,这里我们用的是Mysql-->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<!--查询时是否显示sql语句-->
<prop key="hibernate.show_sql">true</prop>
<!--是否格式化,这里选择格式化的好处是控制台输出的Hibernate语句会分行清晰的显示,否则会一行到底看不清楚,下文附对比图-->
<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="com.ting" entity-manager-factory-ref="entityManagerFactory"/>
<!--能够自动扫描指定包中的文件-->
<context:component-scan base-package="com.ting"/>
</beans>
Hibernate格式化与不格式化对比图(绿色框内的是格式化后的输出):
接着我们只需要写好domain object和测试类,进行单元测试即可:
单元测试通过后我们会发现数据库中已经新建了一个employee
表:
然后我们再修改一下beans.xml中
的事务管理配置、实现repository
接口即可。
Spring Data JPA进阶
- Repository类的定义
public interface Repository<T, ID extends Serializable> {
}
import com.ting.domain.Employee;
import org.springframework.data.repository.Repository;
public interface EmployeeRepository extends Repository<Employee,Integer> {
public Employee findByName(String name);
}
- Repository是一个空接口,这种接口称为标记接口
没有包含方法声明的接口 - 如果我们定义的接口EmployeeRepository继承了核心的接口Repository,那么就是告诉了Spring来管理我们的接口。假如我们没有继承的会,运行就会报错:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.ting.repository.EmployeeRepository' available
这说明你没有继承核心接口,那么Spring就找不到你定义的接口。
另外我们也可以用注释来代替继承语句,作用是一样的:
import com.ting.domain.Employee;
import org.springframework.data.repository.RepositoryDefinition;
@RepositoryDefinition(domainClass = Employee.class,idClass = Integer.class)
public interface EmployeeRepository {
public Employee findByName(String name);
}
- Repository子类接口:CrudRepository,JpaRepository,PagingAndSortingRepository,JpaSpecificationExecutor
在IDEA中crtl+t/h点击相应的类查看继承关系:
- CrudRepository:继承Repository,实现了CRUD相关操作
- PagingAndSortingRepository:继承CrudRepository,实现了分页排序的相关方法
- JpaRepository:继承PagingAndSortingRepository,实现JPA规范相关的方法
- 使用Repository接口查询方法定义规则和使用
// 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> names, Integer age);
// where name in (?,?....) and age <?
public List<Employee> findByNameInAndAgeLessThan(List<String> names, Integer age);
弊端:
1)方法名会比较长: 约定大于配置
2)对于一些复杂的查询,是很难实现
因此,对于这种情况下还是要写SQL语句简单得多。所以又引入了@Query
注解:
@Query("select o from Employee o where id=(select max(id) from Employee t1)")
public Employee getEmployeeByMaxId();
@Query("select o from Employee o where o.name=?1 and o.age=?2")
public List<Employee> queryParams1(String name, Integer age);
@Query("select o from Employee o where o.name=:name and o.age=:age")
public List<Employee> queryParams2(@Param("name")String name, @Param("age")Integer age);
@Query("select o from Employee o where o.name like %?1%")
public List<Employee> queryLike1(String name);
@Query("select o from Employee o where o.name like %:name%")
public List<Employee> queryLike2(@Param("name")String name);
//nativeQuery = true 打开原生态查询
@Query(nativeQuery = true, value = "select count(1) from employee")
public long getCount();
对于修改数据,需要增加Modify注解、并且一定要在事务的管理下才能修改数据,读取不需要事务
@Modifying
@Query("update Employee o set o.age = :age where o.id = :id")
public void update(@Param("id")Integer id, @Param("age")Integer age);
补充知识:【JPQL】–JPQL和SQL的比较
然后在service层:
package com.ting.services;
import com.ting.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service //告诉Spring这是Service层
public class EmployeeService {
@Autowired //注入,注意注入的前提是在beans.xml中定义了<context:component-scan base-package="com.ting"/>上下文扫描路径
private EmployeeRepository employeeRepository;
@Transactional //告诉Spring要添加事务管理,否则无法执行修改数据库的操作
public void update(Integer id,Integer age) {
employeeRepository.update(id,age);
}
}
事务在Spring data中的使用:
1事务一般是在Service层
2@Query、 @Modifying、@Transactional的综合使用
- 使用CrudRepository接口:
这个接口继承源代码所提供的方法:
<S extends T> S save(S entity);
<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();
使用这个接口我们可以实现对数据库的批量操作CRUD
- 使用分页PagingAndSortingRepository接口:
EmployeePagingAndSortingRepositoryTest.java:
package com.ting.services;
import com.ting.domain.Employee;
import com.ting.repository.EmployeeCrudRepository;
import com.ting.repository.EmployeePagingAndSortingRepository;
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;
public class EmployeePagingAndSortingRepositoryTest {
private ApplicationContext ctx = null;
private EmployeePagingAndSortingRepository employeePagingAndSortingRepository = null;
@Before
public void setup() {
ctx = new ClassPathXmlApplicationContext("beans.xml");
employeePagingAndSortingRepository = ctx.getBean(EmployeePagingAndSortingRepository.class);
System.out.println("setup");
}
@After
public void tearDown() {
ctx = null;
System.out.println("tearDown");
}
@Test
public void testPage() {
//注意page参数index从0开始
Pageable pageable = new PageRequest(0,5);
Page<Employee> page = employeePagingAndSortingRepository.findAll(pageable);
System.out.println("查询的总页数" + page.getTotalPages());
System.out.println("查询的总记录数" + page.getTotalElements());
System.out.println("当前第几页" + (page.getNumber()+1));
System.out.println("当前页面的集合" + page.getContent().toString());
System.out.println("当前页面的记录数" + page.getNumberOfElements());
}
}
- 使用JpaRepository接口
接口源代码所提供的方法:
List<T> findAll();
List<T> findAll(Sort sort);
List<T> findAll(Iterable<ID> ids);
<S extends T> List<S> save(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
void deleteInBatch(Iterable<T> entities);
void deleteAllInBatch();
T getOne(ID id);
- 补充一个JpaSpecificationExecutor接口
使用时同时extends JpaRepository<Employee,Integer>,JpaSpecificationExecutor
Specification封装了JPA Criteria查询条件,可以操作分页、排序、条件查询.
@Test
public void testQuery() {
Sort.Order order = new Sort.Order(Sort.Direction.ASC,"id");
Sort sort = new Sort(order);
Pageable pageable = new PageRequest(1,5,sort);
/**
* Root:我们要查询的类型(Employee)
* query:添加查询条件
* cb:构建Predicate
*/
Specification<Employee> specification = new Specification<Employee>() {
@Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//root(employee(age))
Path path = root.get("age");
return cb.gt(path,50); //gt:greater than
}
};
Page<Employee> page = employeeJpaSpecificationRepository.findAll(specification,pageable);
System.out.println("查询的总页数" + page.getTotalPages());
System.out.println("查询的总记录数" + page.getTotalElements());
System.out.println("当前第几页" + (page.getNumber()+1));
System.out.println("当前页面的集合" + page.getContent().toString());
System.out.println("当前页面的记录数" + page.getNumberOfElements());
}