一、spring 数据访问哲学
1、为避免持久化的逻辑分散在程序的各个组件中,数据访问的功能应到放到一个或多个专注于此的组件中,一般称之为数据访问对象(data access object,DAO)。
2、良好的的Repository应以接口的形式向外暴露出去,服务对象通过接口访问Repository对象,这样做可以使服务对象很方便的进行测试,甚至可以创建mock来进行测试。
3、数据访问层应该是与持久化技术无关的方式来进行访问的,这样可以使得切换底层的持久层框架对程序的其他地方所带来的影响最小。
服务对象与Repository之间的关系,服务对象不会处理数据访问,而是将其委托给Repository。
二、spring 的数据访问异常体系
在使用原生的JDBC访问数据时,应用程序必须去捕获JDBC抛出的异常SQLException,而且大多数情况下,异常不会告诉你哪里出了问题、如何处理。
spring JDBC提供的数据访问异常体系解决了这两个问题,spring提供了很多数据访问异常,分别描述了他们抛出时所对应的问题。spring为读取和写入数据的几乎所有错误都提供了异常。而且spring 提供的异常均继承自DataAccessException,该异常为运行时异常,所以在代码中不必强制去捕获。
这样做的好处是:
- 开发者可以从底层繁琐复杂的技术细节中解脱出来。
- 开发者可以选择自己感兴趣的异常进行处理 。
常见的spring异常
异常类 | 说明 |
CleanupFailureDataAccessException | 执行 DAO 操作成功,但在释放数据资源时发生异常,如关闭 Connection 时发生异常。 |
ConcurrencyFailureException | 并发地操作数据时发生异常,如无法获取乐观锁或悲观锁时、死锁引发的失败等场景。 |
DataAccessResourceFailureException | 访问数据资源失败,如无法获取数据连接,无法获取 Hibernate 的会话等场景。 |
DataRetrievalFailureException | 获取数据失败,如找不到对应主键的数据或使用了错误的列索引等场景。 |
DataSourceLookupFailureException | 无法从 JNDI 中查找到数据源。 |
DataIntegrityViolationException | 数据操作违反了数据一致性限制时抛出,如插入重复的主键或引用不存在的外键场景。 |
InvalidDataAccessApiUsageException | 不正确地调用某一种持久化技术时抛出,如在 Spring JDBC 中查询对象在调用前没有事先进行编译操作,就会抛出该异常。这种异常主要是因为不正确地使用持久化技术而产生的。 |
InvalidDataAccessResourceUsageException | 在访问数据源时使用了不正确的方法时抛出,如写错 SQL 语句。 |
PermissionDeniedDataAccessException | 数据访问权限不足时抛出。如仅拥有只读权限却试图更改数 |
UncategorizedDataAccessException | 其它未被分类的异常。 |
................ | ................. |
为了使用spring 的数据访问异常,必须使用spring支持的数据访问模板。
三、数据访问模板
模板方法将过程中与特定实现相关的部分委托给接口,接口的不同实现定义了过程中的具体行为。(设计模式中模板模式的案例?没看过源码,不确定是不是
spring在进行数据访问过程中将固定的和可变的明确划分为两个不同的类:模板和回调。模板管理过程中固定的部分,回调处理自定义的数据访问代码。
模板与回调的职责,模板类处理固定部分:事物控制、管理资源以及异常处理,回调处理语句、绑定参数以及整理结果集。
如果直接使用JDBC,可以使用JdbcTemplate,若使用ORM框架,可以考虑使用JpaTemplate或者HibernateTemplate等。
模板类 | 用途 |
jdbc.core.JdbcTemplate | JDBC链接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名参数的JDBC链接 |
orm.ibatis.SqlMapClientTemplate | IBATIS SqlMap客户端 |
orm.Jpa.JpaTemplate | java持久化API的实体管理器 |
............ | ........... |
spring 提供的数据访问模板
四、数据源的配置
spring多种数据源配置方式
- JDBC驱动程序定义的数据源
- JNDI查找的数据源
- 连接池的数据源
考虑到现在基本都是spring boot的天下了,基本也不使用XML配置了,故此仅给出java配置。
1、JNDI数据源(没用过仅做记录)
java配置:
@Bean
public JndiObjectFactoryBean dataSource(){
JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
factoryBean.setJndiName("jdbc/myProjectDS");//指定JNDI中资源的名称。
factoryBean.setResourceRef(true);//若运行在java服务器中需要设置为true
factoryBean.setProxyInterface(DataSource.class);
return factoryBean;
}
2、数据源连接池
常见的数据库连接池有以下几个:
- Apache commons DBCP
- c3p0
- BoneCP
- Druid
java配置:
@Bean
public BasicDataSource dataSource(){
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/test");
ds.setUsername("root");
ds.setPassword("123");
ds.setInitialSize(5);
ds.setMaxActive(10);
return ds;
}
3、基于JDBC的驱动的数据源
这种数据源是最简单的配置方式,spring 提供了三个这样的数据源类
- DriverManagerDataSource:在每个连接请求时都会返回一个新建的链接
- simpleDriverDataSOurce:与DriverManagerDataSource类似,不同的是他直接使用JDBC驱动
- SingleConnectionDataSource:在每个连接请求时都会返回同一个连接,可认为是只有一个连接的池,该类不适合多线程的应用程序。
@Bean
public DataSource dataSource(){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("org.mysql.Driver");
driverManagerDataSource.setUrl("url");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("1");
return driverManagerDataSource;
}
4、嵌入式数据源,H2
嵌入式数据源作为应用的一部分运行,而不是独立的数据库服务器,对生产环境没有啥用处,但对开发和测试而言是非常好的方案
@Bean
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
五、使用JDBC模板
spring共提供三个JDBC模板:
- JdbcTemplate最基本的springJDBC模板,支持简单的JDBC数据库访问功能以及基于索引的参数查询
- NamedParameterJdbcTemplate:该模板可以将值以命名参数的形式绑定到SQL中,而非简单的索引值。
- SimpleJdbcTepmlate:已被废弃,不做介绍。
1、首先需要配置一个JDBCTemplate,只需为期设置DataSource即可。
代码如下
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
2、编写数据访问相关接口及实现类
图省事,省去了接口类,实际使用过程中自行添加。
UserRepository类
@Repository
public class UserRepository {
private JdbcOperations jdbcOperations;
@Autowired
public UserRepository(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
} public void addUser(User user){
jdbcOperations.update("insert into user (username,password,email) values(?,?,?) ",
user.getUsername(),user.getPassword(),user.getEmail());
}
public User findUser(String id){
return jdbcOperations.queryForObject("select * from user where id = "+id,(resultSet, i) -> User.builder()
.username(resultSet.getString("username"))
.password(resultSet.getString("password"))
.email(resultSet.getString("email"))
.id(resultSet.getLong("id"))
.build());
}
}
UserController类:
@RestController
public class UserController {
private final UserRepository repository; @Autowired
public UserController(UserRepository repository) {
this.repository = repository;
} @GetMapping("/addUser")
public String aa(){
repository.addUser(User.builder()
.email("[email protected]")
.username("tom")
.password("123456")
.build());
return "succ";
} @GetMapping("/query/{id}")
public User queryUser(@PathVariable String id){
return repository.findUser(id);
}
}
NamedParameterJdbcTemplate简单使用:
配置
@Bean
public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource){
return new NamedParameterJdbcTemplate(dataSource);
}
使用map绑定参数
public void addUser(User user){
Map<String,Object> data = new HashMap<>(8);
data.put("username","zhangsan");
data.put("password","123456");
data.put("email","[email protected]");
data.put("id", 123432);
jdbcOperations.update("insert into user(username,password,email,id) values (:username,:password,:email,:id)",data);
}
JdbcOperations API(下次有空了总结哈):https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcOperations.html
Z、参考资料
https://blog.csdn.net/deniro_li/article/details/82820966