Spring 遵循面向对象(OO)原则中的“针对接口编程”。为了避免应用于特定的数据访问策略耦合,编写良好的数据访问对象Repository(或叫做DAO)应该以接口的方式暴露。服务对象通过接口访问 Repository 。这样易于测试,并且,数据访问层是以持久化技术无关的方式进行访问的,可以实现灵活的设计,并且切换持久化框架对应用程序的其他部分带来的影响最小。
JDBC 中的 SQLException
异常,可能包含以下问题:
Hibernate
提供了20多个sql异常,但是是其本身的异常,会造成耦合。
Spring 提供的平台无关的持久化异常
Spring 提供了丰富的数据库访问异常,并且不和特定的持久化方式关联。这些异常都继承自 DataAccessException
是一个非检查性异常,可以选择捕获或者不捕获。
针对不同的平台,Spring 提供了多个模板。如下表:
模板类 (org.springframework.*) | 用途 |
---|---|
jca.cci.core.CciTemplate | JCA CCI 连接 |
jdbc.core.JdbcTemplate | JDBC 连接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名参数的 JDBC |
orm.hibernate3.HibernateTemplate | Hibernate 3.x 以上的Session |
orm.ibatis.SqlMapClientTemplate | IBATIS SqlMap 客户端 |
orm.jdo.JdoTemplate | Java 数据对象(Java Data Object) 实现 |
orm.jpa.JpaTemplate | Java持久化 API 的实体管理器 |
Spring 提供了在 Spring 上下文中配置数据源bean的多种方式,包括:
位于 jee
命名空间下的
元素可以用于检测 JNDI 中的任何对象(包括数据源)并将其作为 Spring 的 bean 。例如配置数据源:
<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/MyDs" resource-ref="true" />
其中,jndi-name
用于指定 JNDI 中资源的名称,如果只设置了 jndi-name 属性,就会根据指定的名称查找数据源。但是,如果程序运行在 Java 应用服务器中,需要将 resource-ref
的属性设置为 true
,这样给定的 jndi-name 将会自动添加 java:comp/env/
前缀。
也可以使用java配置,借助 JndiObjectFactoryBean
从 JNDI 中查找 DataSource。
Spring 没有提供数据源连接池的实现,可以选择其他方案,例如:
类似于Spring自带的DriverManagerDataSource
或 SingleConnectionDataSource
Spring 提供了3个通过 JDBC 定义的数据源类:
DriverManagerDataSource
: 在每个连接请求时都会返回一个新的连接;SimpleDriverDataSource
: 与第一个工作方法类似,但是它是直接使用 JDBC 驱动,来解决在特定环境下的类加载问题,这样的环境包括 ISGi 容器;SingleConnectionDataSource
: 在每个连接请求时都会返回同一个连接。他不是严格意义上的连接池,但是可以看做是只有一个连接的池。配置 DriverManagerDataSource :
/**
* 配置 JDBC 驱动的数据源
* DriverManagerDataSource
*/
@Bean
public DataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("");
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds;
}
@Bean
@Profile("dev")
public DataSource devDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl("url");
dataSource.setUser("user");
dataSource.setPassword("pwd");
return dataSource;
}
@Bean
@Profile("prod")
public DataSource prodDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
JDBC 能够更好的对数据访问的性能调优,允许使用数据库的所有特性,JDBC 能够在更低的层次上处理数据,可以完全控制应用程序如何读取和管理数据,包括访问和管理数据库中单独的列。同样也有其不足之处。
JDBC 大量的代码是用于创建连接语句、资源释放以及异常处理的样板代码。Spring 的 JDBC 框架承担了资源管理和异常处理的工作,从而简化了 JDBC 的代码。
Spring 将数据访问的样板代码抽象到模板类中,为 JDBC 提供了3个模板类:
JdbcTemplate
: 最基本的 JDBC 模板,支持简单的 JDBC 数据库访问功能以及基于索引参数的查询;NamedParameterJdbcTemplate
: 支持查询时可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数;SimpleJdbcTemplate(Spring 3.1 已废弃)
: 利用 Java 5 的一些特性如自动装箱,泛型以及可变参数列表来简化 JDBC 模板的使用。Spring 3.1中,SimpleJdbcTemplate已经被废弃,其Java 5 特性被转移到 JdbcTemplate
中,只有在需要使用命名参数的时候,才需要使用 NamedParameterJdbcTemplate
。对于大多数情况,JdbcTemplate
是最好的选择。
使用 JdbcTemplate 插入数据
配置 JdbcTemplate :
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Repository
public class JdbcUserRepository implements UserReposity {
@Autowired
private JdbcOperations jdbcOperations;
private static final String INSERT_SPITTER = "insert into user (id, user_name, create_user, update_user, create_date, update_date) values (?, ?, ?, ?, ?)";
private static final String SELECT_SPITTER = "select id, user_name from user";
@Override
public User save(User user) {
jdbcOperations.update(INSERT_SPITTER,
user.getId(),
user.getUserName(),
user.getCreateUser(),
user.getUpdateUser(),
new Date(),
new Date());
return user;
}
/**
* 插入记录
* @param user
* @return
*/
private long insertSpitterAndReturnId(User user) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert((JdbcTemplate) jdbcOperations).withTableName("user");
jdbcInsert.setGeneratedKeyName("id");
Map<String, Object> args = new HashMap<String, Object>();
args.put("user_name", user.getUsername());
args.put("updte_date", new Date());
long userId = jdbcInsert.executeAndReturnKey(args).longValue();
return userId;
}
JdbcOperations
是一个接口,定义了JdbcTemplate
所实现的操作,注入接口而非实现类,保持松耦合。
读取数据
/**
* 省略部分代码
*/
@Override
public User findOneById(String id) {
//查询并将结果映射到对象
return jdbcOperations.queryForObject("sql",new UserRowMapper(),id);
}
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setUserName(rs.getString("user_name"));
return user;
}
}
使用 Java8 的 Lambda 表达式
public User findOneByIdLambda(String id) {
return jdbcOperations.queryForObject("sql",
(rs,rowNum) -> {
User user = new User();
user.setId(rs.getString("id"));
user.setUserName(rs.getString("user_name"));
return user;
},id);
}
还可以使用 Java 8 的方法引用,在单独的方法中定义映射逻辑:
/**
* 使用 Java 8 的方法引用
* @param id
* @return
*/
public User findOneById_mapMethod(String id) {
return jdbcOperations.queryForObject("sql", this :: mapUserMapper,id);
}
private User mapUserMapper(ResultSet rs,int row) throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setUserName(rs.getString("user_name"));
return user;
}
使用命名参数
上面的 save()
方法中使用了索引参数,参数的顺序要与参数匹配。而使用命名参数,可以赋予 SQL 中每个参数一个明确的名字,在绑定值到查询语句的时候就通过该名字来引用参数。
例如:
private static final String SQL_INSERT_USER = "insert into user (id,username) values (:id , :username)";