Spring实战第十章:通过Spring和JDBC征服数据库

一、Spring的数据访问哲学

1、Spring自带一组数据访问框架,集成多种数据访问技术;

2、服务对象通过接口来访问Repository,具体数据持久化技术独立于Repository,只有数据访问方法才通过接口进行暴露;

好处:切换持久化框架对应用程序其他部分带来的影响最小,如果将数据访问层的实现细节渗透到应用程序的其他部分中,那整个应用程序将与数据访问层耦合在一起,导致僵尸设计;

3、Spring提供了多种数据访问异常,直接使用Spring的异常体系即可,可以避免与具体持久化方案耦合(如hibernate自己抛的异常)

4、数据访问模板化

Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和 回调(callback);模板管理过程中固定的部分,而回调处理自定义的数据访问代码。

针对不同的持久化平台,Spring提供多种可选模板。如使用JDBC,可使用JdbcTemplate;

下表是Spring所提供的所有数据访问模板及其用途:

模板类(org.springframework.*)用途
jca.cci.core.CciTemplateJCA CCI连接
jdbc.core.JdbcTemplateJDBC连接
jdbc.core.namedparam.NamedParameterJdbcTemplate支持命名参数的JDBC连接
jdbc.core.simple.SimpleJdbcTemplate通过Java 5简化后的JDBC连接(Spring 3.1中已经废弃)
orm.hibernate3.HibernateTemplateHibernate 3.x以上的Session
orm.ibatis.SqlMapClientTemplateiBATIS SqlMap客户端
orm.jdo.JdoTemplateJava数据对象(Java Data Object)实现
orm.jpa.JpaTemplateJava持久化API的实体管理器

二、配置数据源

Spring提供了在Spring上下文中配置数据源bean的多种方式,包括:

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

1、使用JNDI数据源

Web服务器允许配置通过JNDI获取数据源;

好处:

  • 数据源可以在应用程序之外进行管理,应用程序只需在访问数据库的时候查找数据源就可以;
  • 在应用服务器中管理的数据源通常以池的方式组织,具备更好的性能,并且还支持系统管理员对其进行热切换

(1)xml配置方式

<!-- 
jee:jndi-lookup:用于检索JNDI中的任何对象(包括数据源)将其作为Spring中的bean;
jndi-name:指定JNDI中资源名称
resource-ref=true:将对给定的jndi-name自动添加前缀“java:comp/env/”-->
-->
<jee:jndi-lookup id="dataSource"
      jndi-name="/jdbc/SpitterDS" 
  resource-ref="true" />

(2)java配置方式

@Bean
public JndiObjectFactoryBean dataSource(){
    JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
    jndiObjectFB.setJndiName("jdbc/SpitterDS");
    jndiObjectFB.setResourceRef(true);
    jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
    return jndiObjectFB;
}

2、使用数据源连接池

Spring并没有提供数据源连接池实现,但有多项可用方案,包括如下开源的实现:

DBCP配置方式:

<!-- xml方式 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    p:driverClassName="org.h2.Driver"
    p:url="jdbc:h2:tcp://localhost/~/spitter"
    p:username="sa"
    p:password=""
    p:initialSize="5"
    p:maxActive="10"/>
    // Java方式
    @Bean
    public BasicDataSource dataSource(){
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");// 指定JDBC驱动类的全限定名
        ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
        ds.setUsername("sa");
        ds.setPassword("");
        ds.setInitalSize(5);
        ds.setMaxActive(10);
        return ds;
    }

其他可配置属性如下所示:

池配置属性所指定的内容
initialSize池启动时创建的链接数量
maxActive同一时间可从池中分配的最多连接数。如果设置为0,表示无限制
maxIdle池里不会被释放的最多空闲连接数。如果设置为0,表示无限制
maxOpenPreparedStatements在同一时间能够从语句池中分配的预处理语句(prepared statement)的最大数量,如果设置为0,表示无限制
maxWait在抛出异常之前,池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待
minEvictableIdleTimeMillis连接在池中保持空闲而不被回收的最大时间
minIdle在不创建新连接的情况下,池中保持空闲的最小连接数
poolPreparedStatements是否对预处理语句(prepared statement)进行池管理(boolean值)

3、基于JDBC驱动的数据源

在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(均位于org.springframework.jdbc.datasource包中)供选择:

  • DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理。
  • SimpleDriverDataSource:
    与DriverManagerDataSource的工作方式类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器;
  • SingleConnectionDataSource:在每个连接请求时都会返回同一个的连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是可以将其视为只有一个连接的池。

(1)配置方式

以上数据源配置方式同DBCP类似,如下为DriverManagerDataSource配置方式:

a、java方式

@Bean
public DataSource dataSource(){
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
    ds.setUrl("jdbc:mysql://localhost:3306/spitter");
    ds.setUsername("root");
    ds.setPassword("123456");
    return ds;
}

b、xml方式

    <bean id="dataSource" class="com.springframework.jdbc.datasource.DriverManangerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spitter"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

(2)特点:

a、与具备池功能的数据源相比,唯一的区别在于这些数据源bean都没有提供连接池功能,所以没有可配置的池相关的属性。

b、适用于小应用或开发环境来,不要用于生产环境;

SingleConnectionDataSource有且只有一个数据库连接,不适合用于多线程的应用程序,最好只在测试时候使用。

DriverManagerDataSource和SimpleDriverDataSource尽管支持多线程,但是在每次请求连接的时候都会创建新连接,这是性能为代价的。

4、使用嵌入式的数据源

特点:嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器。对于开发和测试来讲,嵌入式数据库是很好的可选方案。

配置方式:

a、xml方式

<?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:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/aop/spring-jdbc-3.1.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
...
    <!-- 可以像其他数据源那样使用它-->
    <jdbc:embedded-database id = "dataSource" type="H2"> 
       <!-- type=H2表明嵌入式数据库是H2数据库
           type=DERBY表明使用嵌入式的Apache Derby数据库
       -->
        <!-- jdbc:script可配置数 >=0 -->
        <jdbc:script location="com/habuma/spitter/db/jdbc/schema.sql" /> <!-- 创建表的sql-->
        <jdbc:script location="com/habuma/spitter/db/jdbc/test-data.sql" /><!-- 创建测试数据的sql-->
    </jdbc:embedded-database>
...
</beans>

b、java方式

@Bean
public DataSource dataSource(){
    return new EmbeddedDatabaseBuilder()
             .setType(EmbeddedDatabaseType.H2)
             .addScript("classpath:schema.sql")
             .addScript("classpath:test-data.sql")
             .build();
}

 

5、使用profile选择数据源

解决不同环境下使用不同数据源问题;

a、java方式

@Configuration
public class DataSourceConfiguration {
    @Profile("development")
    @Bean
    public DataSource embeddedDataSource(){
        return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.H2)
                    .addScript("classpath:schema.sql")
                    .addScript("classpath:test-data.sql")
                    .build();
    }

    @Profile("qa")
    @Bean
    public DataSource Data(){
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc.h2:tcp://localhost/~/spitter");
        ds.setUsername("sa");
        ds.setPassword("");
        ds.setInitialSize(5);
        ds.setMaxActive(10);
        return ds;
    }
    
    @Profile("production")
    @Bean
    public DataSource dataSource(){
            JndiObjectFactoryBean jndiObjectFactoryBean
                    = new JndiObjectFactoryBean();
            jndiObjectFactoryBean.setJndiName("jdbc/SpittrDS");
            jndiObjectFactoryBean.setResourceRef(true);
            jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
            return (DataSource) jndiObjectFactoryBean.getObject();
        }
    }
}

b、xml方式

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ....
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <beans profile="development">
       <jdbc:embedded-database id="dataSource" type="H2">
       <jdbc:script location="com/habuma/spitter/db/jdbc/schema.sql"/>
       <jdbc:script location="com/habuma/spitter/db/jdbc/test-data.sql"/>
       </jdbc:embedded-database>
    </beans>

    <beans profile="qa">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
              p:driverClassName="org.h2.Driver"
              p:url="jdbc:h2:tcp://localhost/~/spitter"
              p:username="sa"
              p:password=""
              p:initialSize="5"
              p:maxActive="10"/>
    </beans>
    
    <beans profile="production">
        <jee:jndi-lookup id="dataSource"
                         jndi-name="/jdbc/SpitterDS"
                         resource-ref="true"/>
    </beans>
</beans>

 

三、在Spring中使用JDBC

使用JDBC模板:Spring的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需编写从数据库读写数据的必须代码。

Spring为JDBC提供了三个模板类供选择:

  • JdbcTemplate:最基本的Spring JDBC模板,这个模板支持简单的JDBC数据库访问功能以及基于索引参数的查询;(推荐)
  • NameParameterJdbcTemplate:使用该模板类执行查询时可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。(只有在需要使用命名参数的时候使用)
  • SimpleJdbcTemplate:该模板类利用Java 5的一些特性自动装箱、泛型以及可变参数列表来简化JDBC模板的使用。(废弃)

1、使用JdbcTemplate操作数据:

(1)配置

// 所引用的dataSource bean可以是javax.sql.DataSource的任意实现,包括了之前文中所创建的
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
    return new JdbcTemplate(dataSource);
}


// 方案一:
@Repository
public class JdbcSpitterRepository implements SpitterRepository{
    private JdbcOperations jdbcOperations;

    @Inject // 注解表示在创建的时候,会自动获得一个JdbcOperations对象
    public JdbcSpitterRepository(JdbcOperations jdbcOperations){
        this.jdbcOperations = jdbcOperations;
    }
...
}

方案二:
@Bean
public SpitterRepository spitterRepository(JdbcTemplate jdbcTemplate){
    return new JdbcSpitterRepository(jdbcTemplate);
}

(2)插入数据

public void addSpitter(Spitter spitter)  {
     jdbcOperations.update("insert into Spitter (username, password, firstname) values (?, ?, ?)",
            spitter.getUsername(),
            spitter.getPassword(),
            spitter.getFirstName();
}

(3)读取数据

/*
queryForObject() 方法有三个参数:
String对象,包含了要从数据库中查找数据的SQL;
RowMapper对象,用来从ResultSet中提取数据并构建域对象(本例中为Spitter);
可变参数列表,列出了要绑定到查询上的索引参数值。
*/
public Spitter findOne(long id) {
    return jdbcOperations.queryForObject(
                "select username, password, firstname from Spitter where id=?",
                new SpitterRowMapper(),  // 将查询结果映射到对象
                id);
}

...

/*
SpitterRowMapper对象实现了RowMapper接口。对于查询返回的每一行数据,JdbcTemplate将会调用
RowMapper的mapRow() 方法,并传入一个ResultSet和包含行号的整数。在SpitterRowMapper的
mapRow()方法中,我们创建了Spitter对象并将ResultSet中的值填充进去。
*/
public static final class SpitterRowMapper implements RowMapper<Spitter>{
    public Spitter mapRow(ResultSet rs, int rowNum) throw SQLException {
        return new Spitter (
            rs.getLong("id"),
            rs.getString("username"),
            rs.getString("password"),
            rs.getString("firstname"));
    }
}

(4)java8方式使用 见P307

 

2、命名参数使用 - NameParameterJdbcTemplate

命名参数可以赋予SQL中的每个参数一个明确的名字,在绑定值在查询语句的时候就通过该名字来引用参数。

配置方式:

@Bean
public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
}
private static final String SQL_INSERT_SPITTER = 
    "insert into spitter (username, password, firstname) " + 
    "values (:username, :password, :firstname)";

public void addSpitter(Spitter spitter) {
    Map<String, Object> paramMap = new HashMap<String, Object>();
    paramMap.put("username", spitter.getUsername()); // 绑定参数
    paramMap.put("password", spitter.getPassword());
    paramMap.put("firstname", spitter.getFirstName());
    jdbcOperations.update(SQL_INSERT_SPITTER, paramMap);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值