Spring框架学习(四)SpringData
说是SpringData,其实其中包含了太多内容,同样开始看的一头雾水,其实现在还是有很多不了解的地方。
官方文档还是讲的不错的,一开始看会比较迷茫,但是稍微看一些以后,有些疑问在里面有说明。这是地址
这里都是基于SpringBoot的自动配置进行的,所以大部分配置比较简单。
spring jdbc
jdbc是java原有的数据访问组件,创建连接、创建查询、执行查询、结果通过ResultSet逐个读取转换。
当前面加上spring
名头后,指的是spring的jdbc框架(或者叫模块?)。通过spring-boot-starter-data-jdbc
依赖,使spring程序中可以得到一个注入得jdbctemplate
对象,而不再依赖DriverMaanger
,而且简化了访问方法。当然底层还是用的jdbc
。
当然jdbc并非ORM,因为还是面向与sql执行结果得,结果也需要手工转换为实体类(或者通过RowMapper)。
配置
配置比较少,基本使用只要配置DataSource就好了。
其实DataSource并非jdbc的概念,它代表一个数据库的配置,后面SpringJPA也要用到。
同样有代码和文件的配置方式,这是使用文件的方式进行配置的:
<?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 name="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url"
value="jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
</beans>
对于这个配置文件多说两句:
- 网上教程需要配置
driver
这个property,比如指向mysql,但是我用的时候,会有日志说不用配,会自动加载,我试了的确如此 - 注意连接字符串里的
serverTimezone=Asia/Shanghai
这个配置,可能是jdbc版本原因,如果不加这个,数据库连接会报时区不对的错误。
注意在springboot入口,需要添加对这个配置文件的依赖:
@SpringBootApplication
@ImportResource("classpath:jdbc-config.xml")
public class App {
public static void main(String[] args) {
new SpringApplicationBuilder(App.class).run(args);
}
}
也可以使用代码的方式,两者相同:
@Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai");
dataSourceBuilder.username("root");
dataSourceBuilder.password("123456");
return dataSourceBuilder.build();
}
配置就是这样,很简单
使用jdbcTemplate
repository需要通过注解标记,主要为了
- 被识别为bean
- 特殊处理其中抛出的数据库异常
@Repository
public class EmployeeRepository {
@Autowired
NamedParameterJdbcTemplate jdbc;
public void createEmployee(Employee employee) {
jdbc.execute("insert into employees (name,email) values (:name,:email)",
new BeanPropertySqlParameterSource(employee), (pc) -> {
pc.execute();
return null;
});
}
public void updateEmployee(long id, Employee employee) {
jdbc.update("update employees set name=:name, email=:email where id=:id", new MapSqlParameterSource()
.addValue("id", id).addValue("name", employee.getName()).addValue("email", employee.getEmail()));
}
public Employee getEmployee(long id) {
return jdbc.queryForObject("select * from employees where id=:id", new MapSqlParameterSource("id", id),
new EmployeeRowMapeer());
}
}
class EmployeeRowMapeer implements RowMapper<Employee> {
@Override
public Employee mapRow(ResultSet rs, int rownumber) throws SQLException {
Employee e = new Employee();
e.setId(rs.getInt(1));
e.setName(rs.getString(2));
e.setEmail(rs.getString(3));
return e;
}
}
这里
- 注册了
NamedParameterJdbcTemplate
,相比普通的JdbcTemplate
,可以支持MapSqlParameterSource
和BeanPropertySqlParameterSource
两个类作为结构化参数传入 - 使用了update/execute等方法执行数据库操作
- 定义了
RowMapper
来转换返回结果到实体类
spring jpa
前言
有一种说法是spring jpa下面使用了jdbc,不过我没有找到相关的资料。
在spring jpa之前,同样的也有一个JPA,它时操作EnitityManager
来进行ORM操作,同样有类似JDBC的复杂性,而SpringJPA封装了这个类到Repository
中,使用方便了。
不过在了解SpringJPA前,JPA也有一些概念,比如JPA有一个需要通过EntityManagerFactory
创建EntityManager
,而EntityManager
内部由各种实现(比如hibernate)维护一个Persistance Context
,里面跟踪了所有的Entity情况。
spring jpa定义了一套接口规范,具体有一些实现组件,比如spring默认的似乎是hibernate。
SpringJPA在使用上以Repository
为基础,有下面这些内容(部分)。
SpringJPA使用spring-boot-starter-data-jpa
作为开始依赖。
实体类
- 通过
@Entity
定义实体类,所以JPA是一种ORM框架,可以将实体与数据库表关联
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
long id;
String name;
String email;
//省略getter setter
}
Repository类
定义了Repository接口,接口有两个模板参数,一个是实体类,一个是实体类的id类型,通过这个信息,找到Repository使用的表。
public interface Repository<T, ID> {
}
直接定义接口名称,Repository可以自动生成实现,比如定义List<Employee> findByName(String name);
,就不用写方法实现,JPA内部会自动生成查询方法,并将参数传入
public interface EmployeeRepository extends Repository<Employee, Long> {
List<Employee> findByName(String name);
}
方法里传入的Sort
和Pageable
参数也能被正确转换为所需的排序或者分页信息。
预定义的Repository
提供了类似CrudRepository接口,定义了常用的findAll,count,findById
等方法;提供了PagingAndSortingRepository
,进一步增加了携带Sort
和Pageable
的相关方法。
应用的Repository集成这些类,常用的CRUD可以不用定义直接使用。
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {}
使用时可以直接使用save方法:
@Autowired
EmployeeRepository repo;
@Override
public void createEmployee(Employee employee) {
repo.save(employee);
}
自定义方法实现:
当通过名称实现不满足要求时,支持JPQL
和SQL
自定义方法。
JPQL
JPQL是JPA定义的SQL,会在Repository初始化的时候就进行预处理,如果其中有错误,这时就会报错。
这是通过JPQL查询的方法:
@Query("select e from #{#entityName} e where name like %?1%")
List<Employee> CustomQuery(String name, Pageable page);
- 参数可以通过位置符号
?1
替代,这里被替换为了name #{#entityName}
被自动替代为实体名字,这样不用写死在代码中- Pageable参数可以被SpringJPA自动识别并加入到最后的查询中
- 注意这是一个Like语句,百分号周围没有加单引号,如果加了就得不到想要的结果了,推测应该是ORM框架自动加的
原生sql
同样可以通过原生SQL:
@Query(value = "SELECT * FROM #{#entityName} WHERE name like %:name%", nativeQuery = true)
List<Employee> NativeQuery(@Param("name") String name);
这里通过nativeQuery
标识标明sql被数据库直接执行。
在此之前,同样可以进行参数替换,这里使用了命名参数的方式@Param("name")
对应到sql中的:name
。
实体名称替换( #{#entityName})同样有效。
同样也需要注意
EntityManager
可以回到java JPA中EntityManager中的方法,对比可以看出来SpringJPA帮忙省略的细节。
注意这里扩展的方法需要实现,所以额外定义一个Cusom接口和Impl类
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>, EmployeeRepositoryCustom {
// @Query(value = "SELECT * FROM #{#entityName} WHERE name like %:name%", nativeQuery = true)
// List<Employee> NativeQuery(@Param("name") String name);
}
interface EmployeeRepositoryCustom {
public List<Employee> NativeQuery(String name);
}
class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {
@PersistenceContext
EntityManager entityManager;
public List<Employee> NativeQuery(String name) {
javax.persistence.Query query = entityManager.createNativeQuery("select * from employees where name like ?1",
Employee.class);
query.setParameter(1, name + "%");
List<Employee> list = query.getResultList();
return list;
}
}
相比较之前的Native SQL
方式,这里同样是创建原生SQL,只是代码要复杂许多,因为暴露出来的接口EmployeeRepository
还需要扩展自自定义的接口EmployeeRepositoryCustom