jdbc【持续更新中】
文章目录
跳转链接(学习路线)及前言(更新中)
java
↓
mysql
↓
jdbc
↓
javaweb
html
css
javascript
javascriptDOM操作
vue
react
基础部分、供复习和平日查询使用,快速复习!!!
内容有误,可以评论区指出,随时改正
简介
- jdbc是(Java Database Connectivity)单词的缩写,翻译为java连接数据库
- jdbc是java程序连接数据库的技术统称
- jdbc由java语言的规范(接口)和各个数据库厂商的实现驱动(jar)组成
- jdbc是一种典型的面向接口编程
- jdbc优势
- 只需要学习jdbc规范接口的方法,即可操作所有的数据库软件
2. 项目中期切换数据库软件,只需要更换对应的数据库驱动jar包,不需要更改代码
安装
mysql版本 | 推荐驱动版本 | 备注 |
mysql 5.5.x | 5.0.x | com.mysql.jdbc.Driver |
mysql 5.7.x | 5.1.x | com.mysql.jdbc.Driver |
msyql 8.x | 8.0.x | 建议: 8.0.25+省略时区设置com.mysql.cj.jdbc.Driver |
下载链接(推荐8.0版本)
部署
添加一个lib文件夹
将jar包复制到该文件夹,添加为项目依赖
流程和讲解
核心api:
DriverManager(驱动管理)、Connection(建立连接)、Statement(发送sql语句,PreparedStatement || CallableStatement)、ResultSet(接收数据的集合类型)
- 注册驱动
DriverManger.registerDriver(new Driver);
注册驱动时会触发两次加载
注意:这里使用8+版本的驱动则需要选中带cj的
这里的registerDriver会注册一次驱动
new Driver也会再次注册一次驱动
所以我们需要将class文件加入到jvm虚拟机内
// 并且这种方式在更换数据库驱动时不用更改代码
Class.forName("com.mysql.cj.jdbc.Driver");
- 获取连接【connection建立连接】
// url:格式 jdbc:数据库厂商名://ip地址:prot/数据库名
Connection connection = DriverManager.getConnection("url","数据库账号","数据库密码");
// 三种格式,getConnection是一个重载方法,可以使用三种方式链接数据库
// 三个参数:
// String url(数据库信息,库)语法:jdbc:数据库管理软件名称[mysql,oracle]://ip地址:port/数据库名?key=value&key=value...
// String user(数据库账号)
// String password(数据库密码)
Connection connection = DriverManager.getConnection("url","jdbc:mysql://127.0.0.1:3306/xia","123456”);
// 两个参数
// String url与三参数的url用法一样
// Properties info存储账号和密码
Properties info = new Properties();
info.put("user","root");
info.put("password","123456");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/xia",info);
//一个参数
// 简写 jdbc:mysql:///xia?user=root&password=123456
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/xia?user=root&password=123456");
// 一些数据库可选参数
serverTimeZone=ASia/Shanghai // 时区设置
useUnicode=true // 是否使用Unicode编码
characterEncoding=utf8 //设置编码格式为utf-8
useSSL // SSL验证
- 创建发送sql语句对象【statement 创建发送sql语句的statement】
// 相当于创建一个容器来装sql语句,并且送到sql内进行查询
Statement statement = connection.createStatement();
- 发送sql语句,并获取返回结果【statement发送sql语句到数据库 并且取得返回结构】
// 准备sql语句
String sql = "select * from 表名;
// 发送,并接收结果集合
ResultSet resultSet = statement.executeQuery(sql);
// statement有很多方法,具体使用哪个,需要看sql语句的类型
// DQL语句使用executeQuery(返回ResultSet结果封装对象)
// SQL分类:DDL(容器创建,修改,删除)DML(插入,修改,删除)DQL(查询)DCL(杈限控制)TPL(事务控制语言)
// executeUpdate(sql) 执行非DQL的语句
- 结果集解析【将result结果解析出来】
// resultSet内部有一个游标,指定当前行的数据,默认游标为第一行,使用next方法可以使游标向后移动一行,如果我们有多行数据,可以使用while(newx()){获取 每一行数据}
// boolean = next()
// true: 有更多行数据,并且向下移动一行
// false: 没有更多行数据
// 光标向上移动relative方法,有很多移动光标的方法,只需要使用next配合while循环即可
// 如果next不满足需求,是查询条件有问题
while (resultSet.next()) {
// 获取数据方法 resultSet.get类型();
// getInt(String(clumnIndex) 传入下角标
// getString(String columnLabel); 传入列名,如果有别名传别名
resultSet.getInt("下角标");
resultSet.getString("字段名");
...
}
- 资源关闭【释放resultset、statement、connection】
resultSet.close();
statement.close();
connection.close();
preparedstatement基本用法
使用statement存在问题:
1.SQL语句需要字符串拼接,比较麻烦
2.只能拼接字符串类型,其他的数据库类型无法处理
3.可能发生注入攻击,statement只能使用静态的语句,否则会导致注入攻击
preparedstatement是预编译前置语句结构。
1、编写SQL语句结果,不包含动态值部分语句,动态值部分使用占位符 “ ?” 替代,注意:“ ?” 只能替代动态值
2、创建preparedstatement,并且传入动态值(先给语句结构,让其知道语句结构,不能再通过注入方式传入)
3、动态值 占位符 “ ?” 赋值
4、发送SQL语句,返回结果
// 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
Connection connection = DriverManager.getConnection("", "", "");
// 1
String sql = "select * from t_user where account = ? and password =?;";
// 2
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 3
// 参数1:index 占位符的位置从左向右数从1开始
// 参数2: object 占位符的值 可以设置任何类型的数据
String account = "123";
String password = "123";
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
// 4
// 这里不需要再传入sql语句了,因为preparedStatement已经有该sql语句,并且动态拼接完成
preparedStatement.executeQuery();
preparedstatement执行DML语句
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///数据库名","root","123456");
// SQL语句
String sql = "insert into table_name(字段1,字段2) values (?,?);";
// 创建preparedStatment,并传入sql
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 占位符赋值
preparedStatement.setObject(1,"测试数据");
preparedStatement.setObject(2,1);
// 发送sql语句
int i = preparedStatement.executeUpdate();
System.out.println(i);
preparedStatement.close();
connection.close();
preparedstatement执行DQL语句
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///数据库名","root","123456");
// SQL语句
String sql = "select * from category;";
// 创建preparedStatment,并传入sql
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 发送sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 遍历数据
while (resultSet.next()){
System.out.println(resultSet.getInt("id"));
}
// 关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
这种遍历数据的方式,如果字段较多时,是比较繁琐的,频繁更改。
咱们只需要把字段也遍历出内容,来动态的遍历数据就会好很多。
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///数据库名","root","123456");
// SQL语句
String sql = "select * from category;";
// 创建preparedStatment,并传入sql
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 发送sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 获取列信息
ResultSetMetaData metaData = resultSet.getMetaData();
// 获取列数
int columnCount = metaData.getColumnCount();
// 遍历表头(字段名)
for (int i = 1; i < columnCount; i++) {
System.out.print(metaData.getColumnLabel(i)+"\t");
}
System.out.println();
while (resultSet.next()){
for (int i = 1; i < columnCount; i++) {
System.out.print(resultSet.getObject(i)+"\t");
}
System.out.println();
}
// 关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
主键回显和主键值获取
返回自增长维护的主键id,就是主键回显
在创建preparedStatement时,传入第二个参数,作用:携带回数据库自增长主键
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///sky_take_out","root","123456");
// SQL语句
String sql = "insert into category(name) values(?);";
// 创建preparedStatment,并传入sql,插入参数,以要求返回主键id
PreparedStatement preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1,"测试的内容1");
// 发送sql语句
int i = preparedStatement.executeUpdate();
// 获取主键id
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();
System.out.println(generatedKeys.getInt(1));
// 遍历表头(字段名)
//略
// 关闭资源
generatedKeys.close();
preparedStatement.close();
connection.close();
批量插入提升性能
// 测试一万次执行效率
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///shuxin","root","123456");
String sql = "insert into user(account,password) values(?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i <= 10000; i++) {
preparedStatement.setObject(1,"ceshi"+i);
preparedStatement.setObject(2,"ceshimima");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)); //结果为 耗时:5793
// 关闭资源
preparedStatement.close();
connection.close();
可以在sql语句后进行拼接
insert into user(account,password) values(?,?),values(?,?),values(?,?)…
记得在数据库路径参数设置rewriteBatchedStatements=true
记得SQL语句不要使用 “ ; ” 结尾 , 否则会报错
// 测试一万次执行效率
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///shuxin?rewriteBatchedStatements=true","root","123456");
String sql = "insert into user(id,account,password) values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i <= 10000; i++) {
preparedStatement.setObject(1,i);
preparedStatement.setObject(2,"ceshi"+i);
preparedStatement.setObject(3,"ceshimima");
// 不执行,将后续的值添加到sql语句后方
preparedStatement.addBatch();
}
// 执行批量操作
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)); //结果为 耗时:171
// 关闭资源
preparedStatement.close();
connection.close();
事务实现
关闭/开启自动提交
// connection是用来操作事务的,statement是对应单一的数据库动作
Connection.setAutoCommit(false/true);
// 数据库操作自动事务提交 开启/关闭
set autocommit = 1/0;
在jdbc中,我们使用try/catch来操作事务是否提交和回滚。当程序报错时,我们就回滚,反之提交
格式
try {
connection.setAutoCommit(false);
//...
//connection操作事务
//statement代表数据库的单一动作
connection.commit();
} catch (Exception e){
connection.rollback();
}
案例
转账案例
1、设计Service类,定义一个金额修改方法,可以根据转账用户名和被转账用户名修改金额
public class BankService {
@Test
public void testTransfer() throws Exception {
transfer("1","2",2000);
}
public void transfer(String addAccount,String subAccount,int money) throws Exception {
BankDao bankDao = new BankDao();
// 转钱
bankDao.addMoney(addAccount,money);
// 减钱
bankDao.subMoney(subAccount,money);
}
}
2、加钱减钱不在Service内操作,而是设计一个Dao类,里面定义两个方法,一个加钱,一个扣钱
public class BankDao {
public void addMoney(String account, int money) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///shuxin", "root", "123456");
// sql语句,创建preparedStatment,并传入sql
String sql = "update bank set money = money + ? where id = ?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 占位符赋值
preparedStatement.setObject(1, money);
preparedStatement.setObject(2, account);
// 发送sql语句
int i = preparedStatement.executeUpdate();
// 反馈结果
if (i > 0) {
System.out.println("加钱成功!");
} else if (i == 0) {
System.out.println("用户名不存在!");
} else {
System.out.println("加钱失败!");
}
}
public void subMoney(String account, int money) throws Exception {
// ...
}
}
这种方式呢,没有事务,当某个账户没有钱了(在表设计字段时,设计为无符号),那么会报错,某个账户则会多出来钱
注意:
一个事务必须在同一个连接(同一个Connection对象),所以我们不能用这种方式来操作事务,需要将两个操作合并到一起。
这段内容注释要重点看喔!
// 修改Service类( 修改内容说明在括号里!!! )
public void transfer(String addAccount,String subAccount,int money) throws Exception {
BankDao bankDao = new BankDao();
// 注册驱动( 这里创建一个Connection,给两个方法传入共用 )
Class.forName("com.mysql.cj.jdbc.Driver");
// 连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///shuxin", "root", "123456");
try{
// 关闭事务自动提交( 不要让数据库自动提交 )
connection.setAutoCommit(false);
// 执行数据库操作( 记得传入connection )
bankDao.addMoney(addAccount,money,connection);
bankDao.subMoney(subAccount,money,connection);
// 提交事务( 执行加钱减钱操作后,执行事务提交 )
connection.commit();
}
catch (Exception e){
// 事务回滚( 执行时,当钱是负数时,虽然加钱是成功的,但减钱操作时失败的,所以会触发catch内的代码,回滚到操作前的数据 )
connection.rollback();
// 异常信息建议抛出,因为该方法也是被Test类调用的
throw e;
}
// 修改Dao类( 修改内容说明在括号里!!! )
// ( 添加一个方法形参,共用一个Connection )
public void addMoney(String account, int money,Connection connection) throws Exception {
// ( 删除Connection连接,在外部调用时传入一个共用的Connection )
String sql = "update bank set money = money + ? where id = ?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1, money);
preparedStatement.setObject(2, account);
int i = preparedStatement.executeUpdate();
if (i > 0) {
System.out.println("加钱成功!");
} else if (i == 0) {
System.out.println("用户名不存在!");
} else {
System.out.println("加钱失败!");
}
// 关闭资源( 不需要将connection关闭,因为这两个方法的操作合并到一个事务,在没有执行完时是不要关闭的 )
preparedStatement.close();
}
public void subMoney(String account, int money,Connection connection) throws Exception {
// ...
}
连接池
简介
druid是阿里开发的一款数据库连接池,他能有效的提升开发速度和运行效率。
我们发现在jdbc中经常使用Connection这个对象,还有关闭资源的对应方法,会大大的浪费效率。
甚至可能连接和关闭资源的占用都比使用的时间长。
druid能帮助我们管理连接和关闭资源
连接池有最大连接数量,到达最大数量时,需要等待一下
市面上有很多连接池产品,Javax.sql.DataSource接口规范了连接池获取连接的方法和回收连接的方法。
使用
准备工作——>
引入jar包,去中央仓库下载
https://mvnrepository.com/search?q=druid
选择一个使用人数较多的
点击这里
选择jar
1、创建druid连接池对选哪个
DruidDataSource dataSource = new DruidDataSource();
2、设置连接池参数
// 必须设置
dataSource.setUrl("jdbc:mysql://localhost:3306/shuxin");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 可选的
// 初始化连接数量
dataSource.setInitialSize(5);
// 最大连接数量
dataSource.setMaxActive(10);
3、获取连接(通用方法,所有的连接池都一样)
// 获取连接
DruidPooledConnection connection = dataSource.getConnection();
4、回收连接(通用方法,所有的连接池都一样)
// 回收资源
connection.close();
// 这里经过连接池,为回收资源
使用配置文件的方式
1、在src路径下创建一个druid.propreties的文件,并写入配置文件
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password = root
url=jdbc:mysql:///shuxin
2、读取外部配置文件
InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(ips);
3、使用连接池的工具类的工程模式,创建连接池
// 使用连接池工具类的工程模式,创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
<br><br>
4、数据库操作
略
5、回收连接
connection.close();
工具类封装
1、在src下,创建一个文件夹用于存放我们自己封装的工具类
2、创建一个工具类
private static DataSource dataSource = null;
static {
// 初始化连接池对象
// 加载外部配置文件
InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties;
try {
properties.load(resourceAsStream);
} catch (IOException e) { throw new RuntimeException(e); }
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) { throw new RuntimeException(e); }
}
// 对外返回连接池方法
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 回收连接方法
public static void freeConnection(Connection connection) throws SQLException {
connection.close();
}
完善工具类
首先,我们在使用我们上面写的工具类,只要调用一次getConnection方法,就会创建一个连接。同时我们是在同一个线程中,也就是说,我们在同一个线程中创建了多个数据库连接。我们应该实现的是,在同一个线程中使用同一个连接(不同方法)。
jdk1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、session等
我们如果不使用这种共享连接的方法,那么在Dao层的方法中必然需要传递一个connection形参,并且在Service层需要创建连接传入,这样不仅代码稍显复杂,也会降低性能。
public class JdbcUtils {
// 数据库来源(存放连接池)
private static DataSource dataSource = null;
// 线程本地变量
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
// 初始化druid
static {
// 读取外部配置文件
InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
// 用于加载
Properties properties = new Properties();
try {
properties.load(resourceAsStream);
} catch (IOException e) { throw new RuntimeException(e); }
// 使用druid工具类的工厂模式创建连接池
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) { throw new RuntimeException(e); } }
// 获取连接
public static Connection getConnection() throws SQLException {
Connection connection = tl.get();
// 第一次连接,线程本地变量是没有连接的
if (connection == null){
// 没有的话获取一个connction
connection = dataSource.getConnection();
tl.set(connection);
}
return connection;
}
// 回收连接
public static void freeConnection() throws SQLException {
Connection connection = tl.get();
if(connection != null){
// 如果线程本地变量是有内容的(连接),那么我们需要将其移除
tl.remove();
// 并且事务设置为true,因为如果设置了手动提交的话,其值为false,所以我们需要设置为true,让其回归默认状态
connection.setAutoCommit(true);
} } }
使用完善后的工具类
bankDao.java
public class BankDao {
public void addMoney(String account, int money,Connection connection) throws Exception {
String sql = "update bank set money = money + ? where id = ?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1, money);
preparedStatement.setObject(2, account);
int i = preparedStatement.executeUpdate();
if (i > 0) {
System.out.println("加钱成功!");
} else if (i == 0) {
System.out.println("用户名不存在!");
} else {
System.out.println("加钱失败!");
}
// 关闭资源
preparedStatement.close();
}
public void subMoney(String account, int money,Connection connection) throws Exception {
// ... } }
bankService.java
public class BankService {
@Test
public void testTransfer() throws Exception {
transfer("2","1",2000);
}
public void transfer(String addAccount,String subAccount,int money) throws Exception {
BankDao bankDao = new BankDao();
Connection connection = JdbcUtils.getConnection();
try{
// 开启事务
connection.setAutoCommit(false);
// 执行数据库操作
bankDao.addMoney(addAccount,money);
bankDao.subMoney(subAccount,money);
// 提交事务
connection.commit();
}
catch (Exception e){
// 事务回滚
connection.rollback();
// 异常信息建议抛出,因为该方法也是被Test类调用的
throw e;
}finally {
// 关闭Connection连接
JdbcUtils.freeConnection();
} } }
非DQL方法封装
jdbc使用步骤
1、注册驱动
2、获取连接
3、编写SQL语句
4、创建satatment(PreparedStatement)
5、占位符赋值
6、发送SQL语句
7、结果解析
8、回收资源
我们的工具类简化了1、2、8的步骤,剩下的我们还需要继续简化,我们每个表都有一个Dao类,当我们在操作表时,重复的内容可以再创建一个类来共用,命名为BaseDao。将重复的代码抽取到BaseDao中。
public int excuteUpdate(String sql, Object... prams) throws SQLException {
// 获取连接
Connection connection = JdbcUtils.getConnection();
// 创建一个小车用来装sql语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
connection.setAutoCommit(false);
try{
// 占位符遍历
for (int i = 0; i < prams.length; i++) {
preparedStatement.setObject(i+1, prams[i]);
}
}
catch (Exception e){
connection.rollback();
e.printStackTrace();
}
connection.commit();
// 发送sql语句
int rows = preparedStatement.executeUpdate();
// 回收
preparedStatement.close();
// 如果autocommit(自动提交为true,代表是自动提交,那么就没有开启事务,可以关闭连接)
if (connection.getAutoCommit()) {
JdbcUtils.freeConnection();
}
return rows;
}
DQL方法封装
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//获取结果集列数
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while(res.next()){
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for(int i=1; i<=columnCount; i++){
//for循环一次,代表取某一行的1个单元格的值
Object value = res.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
//String columnName = metaData.getColumnName(i);//获取第i列的字段名
//这里再取别名可能没办法对应上
String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}