spring in action 通过Spring和JDBC征服 数据库

通过 spring 征服 JDBC

spring 的数据访问哲学

image.png

spring 异常体系

image.png

image.png

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


image.png

针对不同的持久化平台,Spring提供了多个可选的模板。如果直接使用JDBC,那你可以选择JdbcTemplate。如果你希望使用对象关系映射框架,那HibernateTemplate或JpaTemplate可能会更适合你。表10.2列出了Spring所提供的所有数据访问模板及其用途。


image.png

image.png

配置数据源

使用 JDNI 数据
利用Spring,我们可以像使用Spring bean那样配置JNDI中数据源的引用并将其装配到需要的类中。位于jee命名空间下的<jee:jndilookup>元素可以用于检索JNDI中的任何对象(包括数据源)并将其作为Spring的bean。例如,如果应用程序的数据源配置在JNDI中,我们可以使用<jee:jndi-lookup>元素将其装配到Spring中,如下所示:

<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/SpitterDS" resource-ref="true"/>

如果想使用Java配置的话,那我们可以借助JndiObjectFactoryBean从JNDI中查找DataSource:

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

使用数据源连接池
是配置DBCP BasicDataSource的方式:

<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"/>
@Bean
    public BasicDataSource dataSource(){
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
        ds.setUsername("sa");
        ds.setPassword("");
        ds.setInitalSize(5);
        ds.setMaxActive(10);
        return ds;
    }

BasicDataSource 配置属性


image.png

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

-DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理;

-SimpleDriverDataSource:与DriverManagerDataSource的工作方式类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器;

-SingleConnectionDataSource:在每个连接请求时都会返回同一个的连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是你可以将其视为只有一个连接的池。

@Bean
    public DataSource dataSource(){
        DriverMangerDataSource ds = new DriverMangerDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
        ds.setUsername("sa");
        ds.setPassword("");
        return ds;
    }
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="org.h2.Driver"
    p:url="jdbc:h2:tcp://localhost/~/spitter"
    p:username="sa"
    p:password="" />

尽管这些数据源对于小应用或开发环境来说是不错的,但是要将其用于生产环境,你还是需要慎重考虑。因为SingleConnectionDataSource有且只有一个数据库连接,所以不适合用于多线程的应用程序,最好只在测试的时候使用。而DriverManagerDataSource和SimpleDriverDataSource尽管支持多线程,但是在每次请求连接的时候都会创建新连接,这是以性能为代价的。鉴于以上的这些限制,我强烈建议应该使用数据源连接池。

使用嵌入式的数据源
使用jdbc命名空间配置嵌入式数据库

<?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/jdbc/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">
        <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>    

我们将<jdbc:embedded-database>的type属性设置为H2,表明嵌入式数据库应该是H2数据库(要确保H2位于应用的类路径下)。另外,我们还可以将type设置为DERBY,以使用嵌入式的ApacheDerby数据库。

除了搭建嵌入式数据库以外,<jdbc:embedded-database>元素还会暴露一个数据源,我们可以像使用其他的数据源那样来使用它。在这里,id属性被设置成了dataSource,这也是所暴露数据源的bean ID。因此,当我们需要javax.sql.DataSource的时候,就可以注入dataSource bean。
如果使用Java来配置嵌入式数据库时,不会像jdbc命名空间那么简便,我们可以使用EmbeddedDatabaseBuilder来构建

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

使用profile选择数据源
对于开发期来说,<jdbc:embedded-database>元素是很合适的,而在QA环境中,你可能希望使用DBCP的BasicDataSource,在生产部署环境下,可能需要使用<jee:jndi-lookup>。


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

通过使用profile功能,会在运行时选择数据源,这取决于哪一个profile处于激活状态。如程序清单10.2配置所示,当且仅当developmentprofile处于激活状态时,会创建嵌入式数据库,当且仅当qa profile处于激活状态时,会创建DBCPBasicDataSource,当且仅当productionprofile处于激活状态时,会从JNDI获取数据源。

<?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:jee="http://www.springframework.org/schema/jee"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc
        http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
        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 在数据库插入一行

private static final String SQL_INSERT_SPITTER = "insert into spitter (username,password,fullname) values (?,?,?)";
private DataSource dataSource;

public void addSpitter (Spitter spitter){
    Connection conn = null;
    PreparedStatement stmt = null;
    try{
        conn = dataSource.getConnection();
        stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
        stmt.setString(1,spitter.getUsername());
        stmt.setString(2.spitter.getPassword());
        stmt.setString(3,spiiter.getFullName());
        stmt.execute();
        }catch(SQLException e){
            //do somthing...not sure what, though
        }finally{
            try{
                if(stmt != null){
                    stmt.close();
                }
                if(conn != null){
                    conn.close();
                }
            }catch(SQLException e){
                //I'm even less sure about what to do here
            }
        }
}

使用 JDBC 在数据库更新一行

private static final String SQL_UPDATE_SPITTER = "update spitter set username = ?,password = ?, fullname = ? " + "where id =?";

public void saveSpitter(Spitter spitter) {
    Connection conn = null;
    PreparedStatement stmt = null;
    try{
        conn = dataSource.getConnection();
        stmt = conn.prepareStatement(SQL_UPDATE_SPITTER);
        stmt.setString(1,spitter.getUsername());
        stmt.setString(2,spitter.getPassword());
        stmt.setString(3,spitter.getFullname());
        stmt.setLong(4,spitter.getId());
        stmt.execute();
        }catch(SQLException e){
        //still not sure what I'm supposed to do here
        }finally{
            try{
                if(stmt != null){
                    stmt.close();
                }
                if(conn != null){
                    conn.close();
                }
            }catch(SQLException e){
        //or here                                        
            }
        }
}

使用 JDBC 数据库查询一行数据

private static final String SQL_SELECT_SPITTER = "select id username,fullname from spitter where id = ?";
public Spitter findOne(long id) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try{
            conn = dataSource.getConnection();
            stmt = conn.prepareStatement(SQL_SELECT_SPITTER);
            stmt.setLong(1,id);
            rs = stmt.executeQuery();
            Spitter spitter = null;
            if (rs.next()){
                spitter = new Spitter();
                spitter.setId(rs.getLong("id"));
                spitter.setUsername(rs.getString("username"));
                spitter.setUsername(rs.getString("password"));
                spitter.setUsername(rs.getString("fullname"));
            }
            return spitter;
        }catch(SQLException e){
        }finally{
            if(rs != null){
                try{
                    rs.close();
                }catch(SQLException e){}
            }
            if(stmt != null){
                try{
                    stmt.close();
                }catch(SQLException e){}
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){}
            }
        }
}

上面三个示例代码都比较冗长,但是保证了代码的健壮性,下面我们来看看如何使用 JDBC 模板来简化代码:
Spring的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需编写从数据库读写数据的必需代码。

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

使用 JdbcTemplate 插入数据

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
    return new JdbcTemplate(dataSource);
}

现在,我们可以将jdbcTemplate装配到Repository中并使用它来访问数据库。例如,SpitterRepository使用了JdbcTemplate:

@Repository
public class JdbcSpitterRepository implements SpitterRepository{
    private JdbcOperations jdbcOperations;

    @Inject
    public JdbcSpitterRepository(JdbcOperations jdbcOperations){
        this.jdbcOperations = jdbcOperations;
    }
...
}

在这里,JdbcSpitterRepository类上使用了@Repository注解,这表明它将会在组件扫描的时候自动创建。它的构造器上使用了@Inject注解,因此在创建的时候,会自动获得一个JdbcOperations对象。JdbcOperations是一个接口,定义了JdbcTemplate所实现的操作。通过注入JdbcOperations,而不是具体的JdbcTemplate,能够保证JdbcSpitterRepository通过JdbcOperations接口达到与JdbcTemplate保持松耦合。
作为另外一种组件扫描和自动装配的方案,我们可以将JdbcSpitterRepository显式声明为Spring中的bean,如下所示:

@Bean
    public SpitterRepository spitterRepository(JdbcTemplate jdbcTemplate)    {
        return new JdbcSpitterRepository(jdbcTemplate);
    }

基于JdbcTemplate的addSpitter()方法如下:

public void addSpitter(Spitter spitter){
        jdbcOperations.update(INSERT_SPITTER,
                spitter.getUsername(),
                spitter.getPassword(),
                spitter.getFullName(),
                spitter.getEmail(),
                spitter.isUpdateByEmail());
    }

使用JdbcTemplate查询Spitter

public Spitter findOne(long id){
        return jdbcOperations.queryForObject(SELECT_SPITTER_BY_ID,new SpitterRowMapper(),id);
    }
    ...
    private static final class SpitterRowMapper implements RowMapper<Spitter>{
        public Spitter mapRow(ResultSet rs, int rowNum)throws SQLException{
            return new Spitter(rs.getLong("Id"),
                    rs.getString("username"),
                    rs.getString("password"),
                    rs.getString("fullName"),
                    rs.getString("email"),
                    rs.getBoolean("updateByEmail"));
        }
    }

使用Spring JDBC模板的命名参数功能

private static final String INSERT_SPITTER = "insert into Spitter" +
            "(username,password,fullname,email,updateByEmail)" +
            "values" + "(:username,:password,:fullname,:email,:updateByEmail)";
    public void addSpitter(Spitter spitter){
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("username",spitter.getUserName());
        paramMap.put("password",spitter.getPassword());
        paramMap.put("fullname",spitter.getFullName());
        paramMap.put("email",spitter.getEmail());
        paramMap.put("updateByEmail",spitter.isUpdateByEmail());
        jdbcOperation.update(INSERT_SPITTER,paramMap);
    }

如果感兴趣,请关注我的微信公众号:

Capture.PNG
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容