Spring 中的数据库访问

Spring 遵循面向对象(OO)原则中的“针对接口编程”。为了避免应用于特定的数据访问策略耦合,编写良好的数据访问对象Repository(或叫做DAO)应该以接口的方式暴露。服务对象通过接口访问 Repository 。这样易于测试,并且,数据访问层是以持久化技术无关的方式进行访问的,可以实现灵活的设计,并且切换持久化框架对应用程序的其他部分带来的影响最小。

一、Spring 中的数据访问哲学

1、Spring中的数据库访问异常体系

JDBC 中的 SQLException 异常,可能包含以下问题:

  • 应用程序无法连接数据库
  • 语法错误
  • 表或列不存在
  • 插入或更新的数据违反数据库约束

Hibernate提供了20多个sql异常,但是是其本身的异常,会造成耦合。

Spring 提供的平台无关的持久化异常
Spring 提供了丰富的数据库访问异常,并且不和特定的持久化方式关联。这些异常都继承自 DataAccessException 是一个非检查性异常,可以选择捕获或者不捕获。

2、数据访问模板化

针对不同的平台,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的多种方式,包括:

  • 通过 JDBC 驱动程序定义的数据源
  • 通过 JNDI 查找的数据源
  • 连接池的数据源

1、使用 JNDI 数据源

位于 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。

2、使用 数据源连接池

Spring 没有提供数据源连接池的实现,可以选择其他方案,例如:

  • c3p0
  • Apache Commons DBCP
  • BoneCP

类似于Spring自带的DriverManagerDataSourceSingleConnectionDataSource

3、基于 JDBC 驱动的数据源

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;
    }

4、使用 profile 选择数据源

	@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;
    }

三、在 Spring 中使用 JDBC

JDBC 能够更好的对数据访问的性能调优,允许使用数据库的所有特性,JDBC 能够在更低的层次上处理数据,可以完全控制应用程序如何读取和管理数据,包括访问和管理数据库中单独的列。同样也有其不足之处。

1、使用 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)";

你可能感兴趣的:(Spring)