下面在来看一下使用JDBC来实现批处理功能
我们在前面的例子中会发现,每次都是执行一条语句,然后关闭连接,这样效率可能会很低,如果我们想一次插入几千条数据的话,这时候可以使用批处理的功能,所谓批处理就是将多个执行语句进行捆绑然后去执行,但是效率上并非就一定高,因为我们知道这个数据库连接是tcp的,所以在将多个语句捆绑在一起的时候,在传输的过程中也是会进行分包发送的,这个包的大小也不是固定的,这个大小很难掌控的,我们之后经过多次测试之后,才能得到一次批量处理的适宜数量。下面来看一下实例吧:
首先是普通的插入一条数据:
static void create() throws Exception{
//建立一个连接的是很耗时间的
//执行一个sql语句也是很耗时间的
//优化的措施:批处理
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert user(name,birthday,money) values( , , )";
ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
ps.setString(1,"jiangwei");
ps.setDate(2,new Date(System.currentTimeMillis()));
ps.setFloat(3,400);
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs, ps, conn);
}
}
static void createBatch() throws Exception{
//建立一个连接的是很耗时间的
//执行一个sql语句也是很耗时间的
//优化的措施:批处理
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert user(name,birthday,money) values( , , )";
ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//打包的话容量也不是越大越好,因为可能会内存溢出的,同时网络传输的过程中也是会进行拆包传输的,这个包的大小是不一定的
//有时候打包的效率不一定就会高,这个和数据库的类型,版本都有关系的,所以我们在实践的过程中需要检验的
for(int i=0;i<100;i++){
ps.setString(1,"jiangwei");
ps.setDate(2,new Date(System.currentTimeMillis()));
ps.setFloat(3,400);
//ps.addBatch(sql);
ps.addBatch();
}
ps.executeBatch();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs, ps, conn);
}
}
测试代码:
public static void main(String[] args) throws Exception{
long startTime = System.currentTimeMillis();
for(int i=0;i<100;i++){
create();
}
long endTime = System.currentTimeMillis();
System.out.println("For Waste Time:"+(endTime-startTime));
createBatch();
System.out.println("Batch Waste Time:"+(System.currentTimeMillis()-endTime));
}我们在控制台中看到他们分别消耗的时间:

< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ztLDx7/J0tS/tLW91eK49sX6tKbA7c/7usS1xMqxvOTD98/UutzJ2aGjoaO1sci7ztLDx9Tav6rKvLXEyrG68tKyy7W5/cHLo6zV4rj2xfq0psDttcTX7srK0su1xLTz0KHSqtXGv9i6w6GjPC9wPgo8cD48YnI+CjwvcD4KPHA+1NrAtL+00rvPwkpEQkPW0LXEufa2r73hufu8r7rNt9bSs7y8yvU8L3A+CjxwPs7Sw8fU2seww+a1xMD919PW0L/J0tS/tLW9o6zU2rSmwO294bn7vK+1xMqxuvKjrM7Sw8e2vMrH0rvM9dK7zPXP8rrztKbA7bXEo6y1q8rH09DKsbryztLDx9Do0qrIy86qtcS/2NbGveG5+7yvtcS59ravo6yxyMjnztLDx8/rzfnHsLn2tq+jrM/r1rG907aozru1vcTEuPa94bn7vK+8x8K8tciy2df3o6y1sci7SkRCQ9KyysfM4bmpwcvSu8zXQXBpyMPO0sPHwLSy2df3tcQ8L3A+CjxwPjwvcD4KPHByZSBjbGFzcz0="brush:java;">static void test() throws Exception{ Connection conn = null; Statement st = null; ResultSet rs = null; try{ conn = JdbcUtils.getConnection(); //结果集可滚动的 /** * 参数的含义: * ResultSet.RTYPE_FORWORD_ONLY:这是缺省值,只可向前滚动; ResultSet.TYPE_SCROLL_INSENSITIVE:双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。 ResultSet.TYPE_SCROLL_SENSITIVE:双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。 ResultSet.CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet ResultSet.CONCUR_UPDATABLE:指定可以更新 ResultSet */ st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY); rs = st.executeQuery("select id,name,money,birthday from user"); //开始的时候这个游标的位置是第一条记录之前的一个位置 //当执行rs.next的时候这个游标的位置就到第一条记录了 /*while(rs.next()){ //print result }*/ //上面的代码执行之后,这个游标就到最后一条记录的下一个位置了 //所以这里在调用previous方法之后,这个游标就回到了最后一条记录中,所以打印了最后一条记录的值 /*if(rs.previous()){ System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money")); }*/ //绝对定位到第几行结果集 //这里传递的参数的下标是从1开始的,比如这里查询出来的记录有3条,那么这里的参数的范围是:1-3,如果传递的参数不在这个范围内就会报告异常的 rs.absolute(2); System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money")); //滚到到第一行的前面(默认的就是这种情况) rs.beforeFirst(); //滚动到最后一行的后面 rs.afterLast(); rs.isFirst();//判断是不是在第一行记录 rs.isLast();//判断是不是在最后一行记录 rs.isAfterLast();//判断是不是第一行前面的位置 rs.isBeforeFirst();//判断是不是最后一行的后面的位置 //以上的api可以实现翻页的效果(这个效率很低的,因为是先把数据都查询到内存中,然后再进行分页显示的) //效率高的话是直接使用数据库中的分页查询语句: //select * from user limit 150,10; //以上的api实现的分页功能是针对于那些本身不支持分页查询功能的数据库的,如果一个数据库支持分页功能,上面的代码就不能使用的,因为效率是很低的 }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtils.free(rs,st,conn); } }我们看到结果集:rs有很多方法的,我们一次来看一下:
next()方法:这个很常用的,就是将结果集向后滚动
previous()方法:这个方法和next是相反的,将结果集向前滚动
absolute(int index)方法:这个方法是将结果集直接定位到指定的记录上,这个参数是从1开始的,不是0,如果不在指定的范围内的话,会报告异常的
beforeFirst()方法:这个方法是将结果集直接滚动到第一条记录的前面的位置(默认情况是这样的,所以我们每次在取出数据的时候,需要使用next方法,将结果集滚动到第一条记录上)
afterLast()方法:这个方法是将结果集直接滚动到最后一条记录的后面的位置
isFirst()方法:判断是不是在第一行记录
isLast()方法:判断是不是在最后一行记录
isAfterLast()方法:判断是不是第一行前面的位置
isBeforeFirst()方法:判断是不是最后一行的后面的位置
当然我们要向实现可滚动的结果集,还要设置一下参数:
st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);参数的含义:
ResultSet.RTYPE_FORWORD_ONLY:这是缺省值,只可向前滚动;
ResultSet.TYPE_SCROLL_INSENSITIVE:双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。
ResultSet.TYPE_SCROLL_SENSITIVE:双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。
ResultSet.CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet
ResultSet.CONCUR_UPDATABLE:指定可以更新 ResultSet(这个后面会说到)
同时在这里我们只需要使用absolute方法就可以实现分页的功能,因为他可以随便的定位到指定的记录集中,但是这个是在全部将结果集查询处理的基础上来实现的,就是首先将所有符合条件的结果集查询出来放到内存中,然后再就行分页操作,那么这种分页的效率就很低了,所以我们强烈建议在数据库查询数据的时候就进行分页操作,比如MySql中使用limit关键字进行操作,MSSQL中使用top关键字,Oracle中使用number关键字,但是有的数据库中不支持分页查询操作,所以这时候我们只能使用上面的形式来进行分页了。
下面再来看一下JDBC中的可更新以及对更新敏感的结果集操作
我们有时候可能有这样的需求,就是在查询出结果集的时候,想对指定的记录进行更新操作,说白了,就是将查询和更新操作放到一起进行,来看一下代码:
static void test() throws Exception{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
//第三个字段的含义是,在读取数据的时候(已经返回了结果集到内存中了),
//再去修改结果集中的数据,这时候数据库中的数据就可以感知到结果集中的变化了进行修改
st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
rs = st.executeQuery("select * from user");
//这种操作是不可取的,因为查询和更新交互在一起,逻辑就乱了,只有在特定的场合中使用
while(rs.next()){
//这里我们获取到name列的值,如果是lisi我们就将结果集中的他的记录中的money变成170,
//然后再更行行信息,这时候数据库中的这条记录的值也发生变化了,
//内存中的结果集中的记录的值发生改变了,影响到了数据库中的值
String name = rs.getString("name");
if("jiangwei".equals(name)){
rs.updateFloat("money",170);
rs.updateRow();
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs,st,conn);
}
}
我们看到在循环处理结果集的时候,我们将name是jiangwei的记录的钱修改成170,并且反映到数据库中
这里一定要记得设置参数:ResultSet.CONCUR_UPDATABLE,不然会报异常的,这个参数的功能就是将更新的操作同步到数据库中的
这里我们是不建议这种做法的,因为将查询和更新的操作放到一起来操作的话,维护是很差的,我们一定要将CRUD操作进行分开处理,这里只是介绍一下相关知识,不推荐使用的。
下面来看一下通过反射技术,来实现将结果集填充到指定的实体类中,其实这部分的内容很简单的,直接上代码:
/** * 使用泛型 * @param* @param sql * @param clazz * @return * @throws Exception */ static T test(String sql,Class clazz) throws Exception{ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try{ conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); String[] colNames = new String[count]; for(int i=1;i<=count;i++){ colNames[i-1] = rsmd.getColumnLabel(i);//使用别名,让列名和User中的属性名相同 } T user = clazz.newInstance(); //使用反射获取set方法来进行赋值 if(rs.next()){ Method[] ms = user.getClass().getMethods(); for(int i=0;i 测试代码: public static void main(String[] args) throws Exception{ //User user = test("select * from user",User.class); //使用别名来规定列名和属性名相同 User user = test("select id as Id,name as Name,birthday as Birthday,money as Money from user",User.class); System.out.println(user); }其实就是使用反射技术将得到实体类中所有属性的set方法,然后通过set方法进行属性值的填充,这里唯一要注意的问题就是返回的结果集中的字段名称必须要和实体类中的属性名称相同,要做到这一点我们又两种方式:
一种是直接将表中的字段名称和实体类中的属性名称相同
一种是使用别名的方式来操作,将别名设置的和实体类中的属性名称相同
其实我们会发现,后面说到的Hibernate框架就是采用这种机制的
下面在来看一下元数据的相关知识
我们知道元数据信息就是标示数据本身的一些数据信息
1.数据库的元数据信息
就是数据库的相关信息,如数据库的版本号,驱动名称,是否支持事务操作等信息,JDBC提供了接口让我们可以获取这些信息的:
static void test() throws Exception{ Connection conn = JdbcUtils.getConnection(); //这些信息对于那些框架的编写就很有用了,因为框架是要兼容各个数据库类型的,如Hibernate中有一个方言设置 //如果没有设置的话,他就会自己使用以下的api进行查找是那个数据库 DatabaseMetaData metaData = conn.getMetaData(); System.out.println("databaseName:"+metaData.getDatabaseProductName()); System.out.println("driverName:"+metaData.getDriverName()); System.out.println("isSupportBatch:"+metaData.supportsBatchUpdates()); System.out.println("isSupportTransaction:"+metaData.supportsTransactions()); }这些信息对于我们使用人员来说可能没有太大的用处,但是对于开发框架的人来说用处很大的,比如Hibernate框架,他要做到兼容所有的数据库特性的话,必须要将不同特性统一起来,所以他肯定要获取数据库的元数据信息的,后面会说到Hibernate中有一个配置叫做:方言,这个就是用来设置数据库名称的。2.查询参数的元数据信息
当我们在使用PreparedStatement来进行参数填充的时候,我们想知道参数的一些信息,直接上代码:
static void test(String sql,Object[] params) throws Exception{ Connection conn = JdbcUtils.getConnection(); //参数的元数据信息:Statement是没有参数的元数据信息的(因为Statement不支持 ),查看源代码,返回的都是varchar PreparedStatement ps = conn.prepareStatement(sql); //必须在连接数据库中的时候添加这个参数generateSimpleParameterMetadata=true //不然是获取不到参数的,而且会报异常 ParameterMetaData pMetaData= ps.getParameterMetaData(); int count = pMetaData.getParameterCount(); for(int i=1;i<=count;i++){ //因为mysql没有去查询库,所以不能根据查询的字段就能获取字段的类型,所有都返回varchar System.out.println(pMetaData.getParameterClassName(i)); System.out.println(pMetaData.getParameterType(i)); System.out.println(pMetaData.getParameterTypeName(i)); //假定我们传入的参数的顺序和sql语句中的占位符的顺序一样的 ps.setObject(i,params[i-1]); } ps.executeQuery(); }
测试代码:public static void main(String[] args) throws Exception{ String sql = "select name,birthday,money from user where name= "; Object[] params = new Object[]{"jiangwei"}; test(sql,params); }
我们知道Statement是不支持参数填充的,所以不可能获取到参数的元数据信息的我们运行测试代码,会看到如下异常信息:
这时候我们就要注意了,如果想获取到元数据信息的话,我们还需要在连接数据的url后面添加一个参数:
generateSimpleParameterMetadata=true
添加完之后,我们运行结果如下:
我们看到可以获取参数在Java中的类型,12代表的是sql包中的类型:java.sql.Types.VARCHAR,这个字段是个整型值,值就是12,他对应到数据库中varchar类型的
这里要注意一点:有时候我们会发现这里获取到数据库中的类型是错误的,比如这里我们如果将数据库中的name字段的类型修改成char类型的,这里获取到的还是varchar,这一点想一想也是对的,你想想这个是获取查询参数的信息,我们还没有进行查询操作的,系统不可能那么智能的获取到数据库中准确的字段的类型的,所以他这里就做了一个大致的对应关系,将Java中的类型和数据库中的类型对应起来的,因为数据库中char和varchar都是字符串的。所以我们不能相信这里得到的数据库中字段的类型的,需要通过结果集中的元数据类型。
3.结果集中元数据信息
就是查询结果的一般信息,比如字段的相关信息
我们在上面看到要想获取数据库中字段的真实类型的话,只有先进行查询操作才可以,在这里我们就可以获取到正确的类型了,上代码:
static void test(String sql) throws Exception{ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try{ conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); String[] colNames = new String[count]; for(int i=1;i<=count;i++){ //这里是可以获取到真实的类型的,因为这个是已经从数据库中查询了 System.out.println(rsmd.getColumnClassName(i)); System.out.println(rsmd.getColumnName(i)); System.out.println(rsmd.getColumnType(i)); System.out.println(rsmd.getColumnLabel(i));//列的别名:select name as n from user;,有别名的话就返回的是别名,而不是原始的列名了 colNames[i-1] = rsmd.getColumnName(i); } //将结果构建一个Map,列名是key,列的值是value Mapdata = null; //假设查询的数据只有一条,如果是多条的话我们可以定义一个List

