Spring的JDBC模板技术
首先,我们回顾一下我们以前的JDBC操作需要的几个步骤:
DbManager:
getConnection():负责获取数据库连接对象
closeConnection:负责关闭数据库的连接对象
Dao层
我们以insert操作为例子
public int insertNews(News news) {
Connection conn = null;
CallableStatement proc = null;
int backVal = 0;
conn = DbManager.getConnection();
try {
proc = conn.prepareCall("{ call insertNewsPROC(?,?,?) }");
proc.setString(1, news.getTitle());
proc.setString(2, news.getContext());
proc.setInt(3, news.getNewsType().getId());
backVal = proc.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbManager.closeConnection(conn, proc, null);
}
return backVal;
}
这是我们以前的代码,可以看到insert 操作需要做的事情有
1. 获取连接,
2. 执行SQL
3. 关闭连接
如果我们用到Spring的JDBC模板技术以后,我们可以只需要关心的一个步骤就行了, 就是执行SQL
接下来,我们首先利用Spring的IOC技术, 配合使用c3p0连接池, 将我们的DbManager类干掉
c3p0连接池在使用Hibernate的时候我们使用过,不过那时只配置了必要的连接数据库的属性值,其它的都交给Hibernate去完成了.
请看下面的Hibernate中的配置信息
<!-- 设置 c3p0连接池的属性-->
<property name="connection.useUnicode">true</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
<property name="hibernate.c3p0.timeout">5000</property>
<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</property>
<property name="hibernate.c3p0.validate">true</property>
<property name="hibernate.c3p0.max_size">3</property>
<property name="hibernate.c3p0.min_size">1</property>
最主要的是看这里: org.hibernate.connection. C3P0ConnectionProvider
这里说明了在Hibernate中和c3p0打交道的是: C3P0ConnectionProvider,这个类是hibernate提供的.
但是现在我们并没有使用到Hibernate, 而是直接和C3P0打交道,所以我们要找出C3P0中能够提供getConnection的类,然后将此类交给Spring管理即可, 此类是com.mchange.v2.c3p0.ComboPooledDataSource,请看下面的配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" />
<property name="user" value="root" />
<property name="password" value="wdpc" />
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1" />
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="5" />
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1" />
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60" />
<!--当连接池中的连接耗尽的时候c3p0 一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="1" />
</bean>
通过配置此bean,就相当于有了我们以前DbManager类中的getConnection方法, getConnection方法能够返回一个Connection,
只不过我们这里可以获取到一个dataSource 数据源对象
接下来,我们实现一个Dao层,以insert操作为例子:
Dao接口:
public interface UserDao {
public void create(User user) throws Exception;
public void update(User user) throws Exception;
public void delete(int userId) throws Exception;
public User findUserById(int userId) throws Exception;
public List<User> findUserAll() throws Exception;
}
Dao实现类:
public class UserDaoImpl implements UserDao {
private DataSource dataSource;
@Override
public void create(User user) throws Exception {
Connection conn = dataSource.getConnection();
PreparedStatement pst = null;
try {
pst = conn.prepareStatement("insert into users(name) values(?)");
pst.setString(1, user.getName());
pst.executeUpdate();
} catch (Exception e) {
throw e;
} finally {
closeConnection(conn, pst, null);
}
}
@Override
public void delete(int userId) throws Exception {
}
@Override
public List<User> findUserAll() throws Exception {
return null;
}
@Override
public User findUserById(int userId) throws Exception {
return null;
}
@Override
public void update(User user) throws Exception {
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private void closeConnection(Connection conn, Statement st, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Dao层需要提供一个dataSource属性,用来接收注入进来的dataSource,
通过dataSource的dataSource.getConnection()可以获取到Connection对象,然后可以像我们以前一样进行操作
可以看到closeConnection方法还是得我们自己提供,
不过后面如果用到Spring提供的JDBC模板技术的话,这些都可以干掉.
XML配置,将Dao层纳入Spring管理,并且将配置的c3p0数据源对象注入到Dao层对象
<bean id="userDao" class="com.wdpc.sj.dao.impl.UserDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
Test类:
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao)ctx.getBean("userDao");
userDao.create(new User("张三"));
}
}
这时可以看到能够达到插入数据的目的.
Spring的JDBC模板
Spring中提供了一个类: JdbcTemplate类,它能够简化我们的JDBC操作,它封闭了获取连接对象,关闭对象等等一些底层的重复性操作代码.
首先,我们创建JdbcTemplate类,并将其纳入Spring的管理,此类支持构造器注入,不支持set注入
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" />
<property name="user" value="root" />
<property name="password" value="wdpc" />
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1" />
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="5" />
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1" />
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60" />
<!--当连接池中的连接耗尽的时候c3p0 一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="1" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg index="0" ref="dataSource" />
</bean>
<bean id="userDao" class="com.wdpc.sj.dao.impl.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
</beans>
接着我们修改Dao层的代码:
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
@Override
public void create(User user) throws Exception {
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
}
@Override
public void update(User user) throws Exception {
}
@Override
public void delete(int userId) throws Exception {
}
@Override
public List<User> findUserAll() throws Exception {
return null;
}
@Override
public User findUserById(int userId) throws Exception {
return null;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Test :
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao)ctx.getBean("userDao");
userDao.create(new User("武大鹏程"));
}
}
可以看到同样达到了insert操作的效果,但是可以看到Dao层的实现,现在只需要一句话就OK了,这就是jdbcTemplate带来的好处.
接下来,我们补全其它方法的实现.
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
@Override
public void create(User user) throws Exception {
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
}
@Override
public void update(User user) throws Exception {
jdbcTemplate.update("update users set name=? where id=?", new Object[] {
user.getName(), user.getId() }, new int[] { Types.VARCHAR,
Types.INTEGER });
}
@Override
public void delete(int userId) throws Exception {
jdbcTemplate.update("delete from users where id=?",
new Object[] { userId }, new int[] { Types.INTEGER });
}
@Override
public List<User> findUserAll() throws Exception {
return jdbcTemplate.query("select * from users", new UserRowMapper());
}
@Override
public User findUserById(int userId) throws Exception {
return (User) jdbcTemplate.queryForObject(
"select * from users where id=?", new Object[] { userId },
new int[] { Types.INTEGER }, new UserRowMapper());
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
先看看更新操作
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
可以看到执行更新操作的update方法需要四个参数
1. 要执行的SQL语句
2. 打问号的参数
3. 告诉SQL语句参数对应的SQL类型
注意后两个参数是用数组传给jdbcTemplate的
执行查询的方法
queryForObject: 返回单个对象
参数:
1. 要执行的SQL语句
2. 打问题的参数
3. 告诉SQL语句参数对应的SQL类型
4. 结果集的处理方式
query:返回List
用于执行无问号的SQL语句
jdbcTemplate.query(sql, rowMapper);
用于执行有问号的SQL语句
jdbcTemplate.query(sql, args, argTypes, rowMapper)
参数意义和上面的一样
RowMapper是一个接口,这里需要提供一个实现了RowMapper接口的类,它的功能是告诉jdbcTemplate结果集是怎么处理, jdbcTemplate会自己调用此接口中的mapRow方法来处理结果集
代码如下:
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int index) throws SQLException {
return new User(rs.getInt(1),rs.getString(2));
}
}
引申出事务
模拟一个业务层,批量增加User
业务层接口:
public interface UserService {
public void batchCreateUser(String[] names) throws Exception;
}
业务层实现类:
public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new Exception();
}
XML配置:
<bean id="userService" class="com.wdpc.sj.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
Test:
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.batchCreateUser(new String[]{"李四3","李四4"});
}
}
可以看到业务层实现类发生了异常,但是数据还是添加到数据库中去了,这时可以得知jdbcTemplate并未集成事务,怎么给服务层加入事务的支持呢,请看下一章节.
首先,我们回顾一下我们以前的JDBC操作需要的几个步骤:
DbManager:
getConnection():负责获取数据库连接对象
closeConnection:负责关闭数据库的连接对象
Dao层
我们以insert操作为例子
public int insertNews(News news) {
Connection conn = null;
CallableStatement proc = null;
int backVal = 0;
conn = DbManager.getConnection();
try {
proc = conn.prepareCall("{ call insertNewsPROC(?,?,?) }");
proc.setString(1, news.getTitle());
proc.setString(2, news.getContext());
proc.setInt(3, news.getNewsType().getId());
backVal = proc.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbManager.closeConnection(conn, proc, null);
}
return backVal;
}
这是我们以前的代码,可以看到insert 操作需要做的事情有
1. 获取连接,
2. 执行SQL
3. 关闭连接
如果我们用到Spring的JDBC模板技术以后,我们可以只需要关心的一个步骤就行了, 就是执行SQL
接下来,我们首先利用Spring的IOC技术, 配合使用c3p0连接池, 将我们的DbManager类干掉
c3p0连接池在使用Hibernate的时候我们使用过,不过那时只配置了必要的连接数据库的属性值,其它的都交给Hibernate去完成了.
请看下面的Hibernate中的配置信息
<!-- 设置 c3p0连接池的属性-->
<property name="connection.useUnicode">true</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
<property name="hibernate.c3p0.timeout">5000</property>
<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</property>
<property name="hibernate.c3p0.validate">true</property>
<property name="hibernate.c3p0.max_size">3</property>
<property name="hibernate.c3p0.min_size">1</property>
最主要的是看这里: org.hibernate.connection. C3P0ConnectionProvider
这里说明了在Hibernate中和c3p0打交道的是: C3P0ConnectionProvider,这个类是hibernate提供的.
但是现在我们并没有使用到Hibernate, 而是直接和C3P0打交道,所以我们要找出C3P0中能够提供getConnection的类,然后将此类交给Spring管理即可, 此类是com.mchange.v2.c3p0.ComboPooledDataSource,请看下面的配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" />
<property name="user" value="root" />
<property name="password" value="wdpc" />
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1" />
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="5" />
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1" />
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60" />
<!--当连接池中的连接耗尽的时候c3p0 一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="1" />
</bean>
通过配置此bean,就相当于有了我们以前DbManager类中的getConnection方法, getConnection方法能够返回一个Connection,
只不过我们这里可以获取到一个dataSource 数据源对象
接下来,我们实现一个Dao层,以insert操作为例子:
Dao接口:
public interface UserDao {
public void create(User user) throws Exception;
public void update(User user) throws Exception;
public void delete(int userId) throws Exception;
public User findUserById(int userId) throws Exception;
public List<User> findUserAll() throws Exception;
}
Dao实现类:
public class UserDaoImpl implements UserDao {
private DataSource dataSource;
@Override
public void create(User user) throws Exception {
Connection conn = dataSource.getConnection();
PreparedStatement pst = null;
try {
pst = conn.prepareStatement("insert into users(name) values(?)");
pst.setString(1, user.getName());
pst.executeUpdate();
} catch (Exception e) {
throw e;
} finally {
closeConnection(conn, pst, null);
}
}
@Override
public void delete(int userId) throws Exception {
}
@Override
public List<User> findUserAll() throws Exception {
return null;
}
@Override
public User findUserById(int userId) throws Exception {
return null;
}
@Override
public void update(User user) throws Exception {
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private void closeConnection(Connection conn, Statement st, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Dao层需要提供一个dataSource属性,用来接收注入进来的dataSource,
通过dataSource的dataSource.getConnection()可以获取到Connection对象,然后可以像我们以前一样进行操作
可以看到closeConnection方法还是得我们自己提供,
不过后面如果用到Spring提供的JDBC模板技术的话,这些都可以干掉.
XML配置,将Dao层纳入Spring管理,并且将配置的c3p0数据源对象注入到Dao层对象
<bean id="userDao" class="com.wdpc.sj.dao.impl.UserDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
Test类:
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao)ctx.getBean("userDao");
userDao.create(new User("张三"));
}
}
这时可以看到能够达到插入数据的目的.
Spring的JDBC模板
Spring中提供了一个类: JdbcTemplate类,它能够简化我们的JDBC操作,它封闭了获取连接对象,关闭对象等等一些底层的重复性操作代码.
首先,我们创建JdbcTemplate类,并将其纳入Spring的管理,此类支持构造器注入,不支持set注入
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" />
<property name="user" value="root" />
<property name="password" value="wdpc" />
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1" />
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="5" />
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1" />
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60" />
<!--当连接池中的连接耗尽的时候c3p0 一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="1" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg index="0" ref="dataSource" />
</bean>
<bean id="userDao" class="com.wdpc.sj.dao.impl.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
</beans>
接着我们修改Dao层的代码:
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
@Override
public void create(User user) throws Exception {
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
}
@Override
public void update(User user) throws Exception {
}
@Override
public void delete(int userId) throws Exception {
}
@Override
public List<User> findUserAll() throws Exception {
return null;
}
@Override
public User findUserById(int userId) throws Exception {
return null;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Test :
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao)ctx.getBean("userDao");
userDao.create(new User("武大鹏程"));
}
}
可以看到同样达到了insert操作的效果,但是可以看到Dao层的实现,现在只需要一句话就OK了,这就是jdbcTemplate带来的好处.
接下来,我们补全其它方法的实现.
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
@Override
public void create(User user) throws Exception {
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
}
@Override
public void update(User user) throws Exception {
jdbcTemplate.update("update users set name=? where id=?", new Object[] {
user.getName(), user.getId() }, new int[] { Types.VARCHAR,
Types.INTEGER });
}
@Override
public void delete(int userId) throws Exception {
jdbcTemplate.update("delete from users where id=?",
new Object[] { userId }, new int[] { Types.INTEGER });
}
@Override
public List<User> findUserAll() throws Exception {
return jdbcTemplate.query("select * from users", new UserRowMapper());
}
@Override
public User findUserById(int userId) throws Exception {
return (User) jdbcTemplate.queryForObject(
"select * from users where id=?", new Object[] { userId },
new int[] { Types.INTEGER }, new UserRowMapper());
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
先看看更新操作
jdbcTemplate.update("insert into users(name) values(?)",
new Object[] { user.getName() }, new int[] { Types.VARCHAR });
可以看到执行更新操作的update方法需要四个参数
1. 要执行的SQL语句
2. 打问号的参数
3. 告诉SQL语句参数对应的SQL类型
注意后两个参数是用数组传给jdbcTemplate的
执行查询的方法
queryForObject: 返回单个对象
参数:
1. 要执行的SQL语句
2. 打问题的参数
3. 告诉SQL语句参数对应的SQL类型
4. 结果集的处理方式
query:返回List
用于执行无问号的SQL语句
jdbcTemplate.query(sql, rowMapper);
用于执行有问号的SQL语句
jdbcTemplate.query(sql, args, argTypes, rowMapper)
参数意义和上面的一样
RowMapper是一个接口,这里需要提供一个实现了RowMapper接口的类,它的功能是告诉jdbcTemplate结果集是怎么处理, jdbcTemplate会自己调用此接口中的mapRow方法来处理结果集
代码如下:
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int index) throws SQLException {
return new User(rs.getInt(1),rs.getString(2));
}
}
引申出事务
模拟一个业务层,批量增加User
业务层接口:
public interface UserService {
public void batchCreateUser(String[] names) throws Exception;
}
业务层实现类:
public void batchCreateUser(String[] names) throws Exception {
for(String name : names){
userDao.create(new User(name));
}
throw new Exception();
}
XML配置:
<bean id="userService" class="com.wdpc.sj.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
Test:
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.batchCreateUser(new String[]{"李四3","李四4"});
}
}
可以看到业务层实现类发生了异常,但是数据还是添加到数据库中去了,这时可以得知jdbcTemplate并未集成事务,怎么给服务层加入事务的支持呢,请看下一章节.