10.Spring集成JDBC
  Op9yysgqYUmV 2023年11月02日 65 0


相信大家都很熟悉jdbc的开发过程了,jdbc的开发繁琐,易错,虽然我们可以重构自己的一套JDBC模板,从而能简化日常开发,但自己开发的JDBC模板不够通用,而且对于每一套JDBC模板实现都差不多,从而导致开发人员必须掌握每一套模板。

Spring JDBC提供了一套JDBC抽象框架,用于简化JDBC开发,而且如果各个公司都使用该抽象框架,开发人员首先减少了学习成本,直接上手开发。

10.Spring集成JDBC_Java

Spring对JDBC的支持

 

Spring通过抽象JDBC访问并提供一致的API来简化JDBC编程的工作量。我们只需要声明SQL、调用合适的Spring JDBC框架API、处理结果集即可。事务由Spring管理,并将JDBC受检异常转换为Spring一致的非受检异常,从而简化开发。

Spring主要提供JDBC模板方式、关系数据库对象化方式来简化JDBC编程:

  • 关系数据库操作对象化方式:Spring JDBC框架提供了将关系数据库操作对象化的表示形式,从而使用户可以采用面向对象编程来完成对数据库的访问;如MappingSqlQuery、SqlUpdate、SqlCall、SqlFunction、StoredProcedure等类。这些类的实现一旦建立即可重用并且是线程安全的。

Spring JDBC还提供了一些强大的工具类,如DataSourceUtils来在必要的时候手工获取数据库连接等。

Spring的JDBC架构

Spring JDBC抽象框架由四部分组成:datasource、support、core、object,如图

10.Spring集成JDBC_bc_02

 

  • support包:提供将JDBC异常转换为DAO非检查异常转换类、一些工具类如JdbcUtils等。
  • datasource包:提供简化访问JDBC 数据源(javax.sql.DataSource实现)工具类,并提供了一些DataSource简单实现类从而能使从这些DataSource获取的连接能自动得到Spring管理事务支持。
  • core包:提供JDBC模板类实现及可变部分的回调接口,还提供SimpleJdbcInsert等简单辅助类。
  • object包:提供关系数据库的对象表示形式,如MappingSqlQuery、SqlUpdate、SqlCall、SqlFunction、StoredProcedure等类,该包是基于core包JDBC模板类实现。

JDBC模板类

Spring JDBC抽象框架core包提供了JDBC模板类,其中JdbcTemplate是core包的核心类,其他模板类都是基于它封装完成的,JDBC模板类是第一种工作模式。

JdbcTemplate类通过模板设计模式帮助我们消除了冗长的代码,只做需要做的事情(即可变部

分),并且帮我们做那些固定部分,如连接的创建及关闭。

JdbcTemplate类对可变部分采用回调接口方式实现,如ConnectionCallback通过回调接口返回给用户一个连接,从而可以使用该连接做任何事情、StatementCallback通过回调接口返回给用户一个Statement,从而可以使用该Statement做任何事情等等,还有其他一些回调接口如图:

10.Spring集成JDBC_模板类_03

Spring除了提供JdbcTemplate核心类,还提供了基于JdbcTemplate实现的NamedParameterJdbcTemplate类用于支持命名参数绑定、 SimpleJdbcTemplate类用于支持

 

Java5+的可变参数及自动装箱拆箱等特性。

使用JdbcTemplate模板类

在使用JdbcTemplate模板类时必须通过DataSource获取数据库连接,Spring JDBC提供了

DriverManagerDataSource实现,它通过包装“DriverManager.getConnection”获取数据库连接。

Spring JDBC通过实现DaoSupport来支持一致的数据库访问。Spring JDBC提供如下DaoSupport实现:

• JdbcDaoSupport:用于支持一致的JdbcTemplate访问;

• NamedParameterJdbcDaoSupport:继承JdbcDaoSupport,同时提供NamedParameterJdbcTemplate访问;

• SimpleJdbcDaoSupport:继承JdbcDaoSupport,同时提供SimpleJdbcTemplate访问。

由于JdbcTemplate、NamedParameterJdbcTemplate、SimpleJdbcTemplate类使用DataSourceUtils获取及释放连接,而且连接是与线程绑定的,因此这些JDBC模板类是线程安全的,即JdbcTemplate对象可以在多线程中重用。

接下来看一下Spring JDBC框架的最佳实践:

在项目pom.xml文件里添加jdbc依赖

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.13.RELEASE</version>
    </dependency>
<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.26</version>
</dependency>

下面基于JdbcTemplate模板类,使用mysql数据库进行下测试,在使用JdbcTemplate模板类时必须通过DataSource获取数据库连接,Spring JDBC提供了DriverManagerDataSource实现,它通过包装“DriverManager.getConnection”获取数据库连接

1)首先定义Dao接口

public interface IUserDao {
    int insert(User user);
    int delete(Integer uid);
    int update(User user);
    List<User> getAll();
    User getById(Integer id);
}

2)定义Dao实现,此处是使用Spring JDBC实现:

public class UserJdbcDaoImpl extends SimpleJdbcDaoSupport implements IUserDao {
    private static final String INSERT_SQL = "insert into test(name) values(:myName)";
    private static final String COUNT_ALL_SQL = "select count(*) from test";    
    @Override
    public void save(UserModel model) {
        getSimpleJdbcTemplate().update(INSERT_SQL, new BeanPropertySqlParameterSource(model));
    }
    @Override
    public int countAll() {
        return getJdbcTemplate().update(COUNT_ALL_SQL);
    }  
}

3)进行资源配置

resources.properties:

db.driver.class=com.mysql.cj.jdbc.Driver

db.url=jdbc:mysql://127.0.0.1/first_db?characterEncoding=utf-8

db.username=root

db.password=9958

applicationContext-jdbc.xml:

<!--    读取配置文件-->
<context:property-placeholder location="classpath:db/resources.properties"/>
<!-- 1、声明数据源对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <!-- 加载jdbc驱动 -->
    <property name="driverClassName" value="${db.driver.class}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    <property name="maxWait" value="3000"/>
    <property name="maxActive" value="100"/>
    <!--Druid监控-->
    <property name="filters" value="wall,stat"/>
</bean>

<!--创建 JDBCTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<bean id="abstractDao" abstract="true">
    <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDaoImpl"
      class="com.spring.db.dao.impl.UserDaoImpl"
      parent="abstractDao"/>

写个测试:

public class JdbcTemplateTest {
    private static UserDaoImpl userDao;

    @BeforeClass
    public static void setUpClass() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("db/applicationContext-jdbc.xml");
        userDao = ctx.getBean("userDaoImpl", UserDaoImpl.class);
    }

    /*增*/
    @Test
    public void add() {
        User user=new User();
        user.setName("lily");
        user.setSex("m");
        userDao.insert(user);
    }

JdbcTemplate主要提供以下五类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;
  • batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

JdbcTemplate类支持的回调类

  • 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;

PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;

CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;

  • 预编译语句设值回调:用于给预编译语句相应参数设值;

PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;

BatchPreparedStatementSetter:类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;

  • 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;

ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;

StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;

PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;

CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;

  • 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;

RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法

mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。

RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。

ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;

1)预编译语句及存储过程创建回调、自定义功能回调使用:

@Test
public void testPreparedStatement1() {
    int count = jdbcTemplate.execute(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection conn)
                throws SQLException {
            return conn.prepareStatement("select count(*) from user");
        }
    }, new PreparedStatementCallback<Integer>() {
        @Override
        public Integer doInPreparedStatement(PreparedStatement pstmt)
                throws SQLException, DataAccessException {
            pstmt.execute();
            ResultSet rs = pstmt.getResultSet();
            rs.next();
            return rs.getInt(1);
        }
    });
    System.out.println("count========="+count);
}

首先使用PreparedStatementCreator创建一个预编译语句,其次由JdbcTemplate通过PreparedStatementCallback回调传回,由用户决定如何执行该PreparedStatement。此处我们使用的是execute方法。

2)预编译语句设值回调使用:

@Test
public void testPreparedStatement2() {
    String insertSql = "insert into user(name,sex) values (?,?)";
    int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement pstmt) throws SQLException {
            pstmt.setObject( 1,"marry");
            pstmt.setObject( 2,"m");
        }
    });
    System.out.println("count======"+count);

    String deleteSql = "delete from user where name=?";
    count = jdbcTemplate.update(deleteSql, new Object[]{"marry"});
    System.out.println(count);
}

通过JdbcTemplate的update(String sql, PreparedStatementSetter pss)执行预编译sql,其中sql参数为“insert into user(name,sex) values (?,?) ”,该sql有两个占位符需要在执行前设值,PreparedStatementSetter实现就是为了设值,使用setValues(PreparedStatement pstmt)回调方法设值相应的占位符位置的值。JdbcTemplate也提供一种更简单的方式“update(String sql,Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter。

3)结果集处理回调:

@Test
public void testResultSet1() {
    jdbcTemplate.update("insert into user(name,sex) values('test','f')");
    String listSql = "select * from user";
    List result = jdbcTemplate.query(listSql, new RowMapper<Map>() {
        @Override
        public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
            Map row = new HashMap();
            row.put(rs.getInt("id"), rs.getString("name"));
            return row;
        }
    });
    System.out.println(result.size());
    jdbcTemplate.update("delete from user where name='test'");

}

RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法将结果集的每一行转换为一个Map,当然可以转换为其他类,如表的对象画形式。

@Test
public void testResultSet2() {
    jdbcTemplate.update("insert into user(name,sex) values('test','f')");
    String listSql = "select * from user";
    final List result = new ArrayList();
    jdbcTemplate.query(listSql, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            Map row = new HashMap();
            row.put(rs.getInt("id"), rs.getString("name"));
            result.add(row);
        }

    });
    System.out.println(result.size());
    jdbcTemplate.update("delete from user where name='test'");

}

RowCallbackHandler接口也提供方法processRow(ResultSet rs),能将结果集的行转换为需要的形式。

@Test
public void testResultSet3() {
    jdbcTemplate.update("insert into user(name,sex) values('test','f')");
    String listSql = "select * from user";
    List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() {
        @Override
        public List extractData(ResultSet rs) throws SQLException, DataAccessException {
            List result = new ArrayList();
            while (rs.next()) {
                Map row = new HashMap();
                row.put(rs.getInt("id"), rs.getString("name"));
                result.add(row);
            }
            return result;
        }
    });
    System.out.println(result.size());
    jdbcTemplate.update("delete from user where name='test'");
}

ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集。

当然JdbcTemplate提供更简单的queryForXXX方法,来简化开发:

// 查询一行数据并将该行数据转换为Map返回

jdbcTemplate.queryForMap("select * from user where name='name5'");

//查询一行任何类型的数据,最后一个参数指定返回结果类型

jdbcTemplate.queryForObject("select count(*) from user", Integer.class);

//查询一批数据,默认将每行数据转换为Map

jdbcTemplate.queryForList("select * from user");

//只查询一列数据列表,列类型是String类型,列名字是name

jdbcTemplate.queryForList("

select name from user where name=?", new Object[]{"name5"}, String.class);

//查询一批数据,返回为SqlRowSet,类似于ResultSet,但不再绑定到连接上

SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from user");

NamedParameterJdbcTemplate

NamedParameterJdbcTemplate类是基于JdbcTemplate类,并对它进行了封装从而支持命名参数特性。NamedParameterJdbcTemplate主要提供以下三类方法:execute方法、query及queryForXXX方法、update及batchUpdate方法。首先让我们看个例子吧:

@Test
    public void testNamedParameterJdbcTemplate1() {
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
        namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
        String insertSql = "insert into user(name,sex) values(:name,:sex)";
        String selectSql = "select * from user where name=:name";
        String deleteSql = "delete from user where name=:name";
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("name", "name6");
        paramMap.put("sex", "f");
        namedParameterJdbcTemplate.update(insertSql, paramMap);

        final List<Integer> result = new ArrayList<Integer>();
        namedParameterJdbcTemplate.query(selectSql, paramMap, new RowCallbackHandler() {

            @Override
            public void processRow(ResultSet rs) throws SQLException {
                result.add(rs.getInt("id"));
            }
        });
        assertEquals(1, result.size());
        SqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
        namedParameterJdbcTemplate.update(deleteSql, paramSource);

    }

接下来让我们分析一下代码吧:

1)NamedParameterJdbcTemplate初始化:可以使用DataSource或JdbcTemplate 对象作为构造器参数初始化;

2)insert into user(name,sex) values(:name,:sex):其中“:name”就是命名参数;

3) update(insertSql, paramMap):其中paramMap是一个Map类型,包含键为“name”,值为“name5”的键值对,也就是为命名参数设值的数据;

4)query(selectSql, paramMap, new RowCallbackHandler()……):类似于JdbcTemplate中介绍的,唯一不同是需要传入paramMap来为命名参数设值;

5)update(deleteSql, paramSource):类似于“update(insertSql, paramMap)”,但使用SqlParameterSource参数来为命名参数设值,此处使用MapSqlParameterSource实现,其就是简单封装java.util.Map。

NamedParameterJdbcTemplate类为命名参数设值有两种方式:java.util.Map和SqlParameterSource:

1)java.util.Map:使用Map键数据来对于命名参数,而Map值数据用于设值;

2)SqlParameterSource:可以使用SqlParameterSource实现作为来实现为命名参数设值,默认有MapSqlParameterSource和BeanPropertySqlParameterSource实现;

MapSqlParameterSource实现非常简单,只是封装了java.util.Map;而BeanPropertySqlParameterSource封装了一个JavaBean对象,通过JavaBean对象属性来决定命名参数的值。

public class UserModel {
    private int id;
    private String myName;
    private String sex;
    //省略getter和setter
}
@Test
public void testNamedParameterJdbcTemplate2() {
    NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
    namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
    UserModel model = new UserModel();
    model.setMyName("name5");
    String insertSql = "insert into test(name) values(:myName)";
    SqlParameterSource paramSource = new BeanPropertySqlParameterSource(model);
    namedParameterJdbcTemplate.update(insertSql, paramSource);
}

可以看出BeanPropertySqlParameterSource使用能减少很多工作量,但命名参数必须和JavaBean属性名称相对应才可以。

 

参考:



http://sishuok.com/forum/blogPost/list/0/2489.html

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
Op9yysgqYUmV