JDBC学习笔记
2015-12-21 14:51
417 查看
JDBC是什么?为什么要使用JDBC呢?
大家都知道,有各种数据库可以使用,如mysql、oracle、DB2等,那么在程序开发过程中,如何保证程序与数据库的独立性,以及降低访问数据库的操作繁杂性,引入了JDBC(Java Data Base Connectivity),相当于一种介质,负责java程序与数据库的沟通。JDBC实际上提供了一套标准,一套接口,各数据厂商安装这个标准去开发自己的数据库软件,从而隐藏了各数据库软件底层的差异,使用者只需调用这些标准的接口,就能完成一系列对数据库的操作。
Driver接口中有connect(String url, Properties info)方法,负责与数据连接,并返回相应的连接Connection类对象。
数据库url的格式:
jdbc:mysql://localhost:3306/test
协议:子协议://地址:端口号/数据库的表
但是直接使用Driver接口,必须要与其实现类关联,可以使用配置文件降低耦合,在配置文件中写入Driver的实现类的全类名,在程序中使用反射获得实现类对象。
所以为了降低耦合,以及更方便的与数据库相连,一般使用DriverManager访问数据库,DriverManager的底层实现就是依赖Driver接口的实现,Driver接口一般不直接使用。
1、在配置文件中配置四要素:驱动全类名、数据库url、user、password,并在程序中利用Properties类获取到
2、使用反射加载数据库驱动 :Class.forName或者DriverManager的registerDriver方法
3、使用DriverManager的getConnection方法获取连接
注意:mysql与oracle的url和驱动实现类的区别
mysql:
url jdbc:mysql://localhost:3306/test (默认可写为 jdbc:mysql:///test)
全类名: com.mysql.jdbc.Driver
oracle:
url jdbc:oracle:thin:@localhost:1521:orcl(待检验)
全类名: oracle.jdbc.driver.OracleDriver
1、建立连接、关闭连接(建立之后顺手关闭,中间再插入代码,为了不忘记关闭,因为建立的连接以及Statement都是应用程序与数据库连接的资源,要及时释放)
2、建立Statement,释放statement
3、准备sql语句
4、执行sql语句
也可以是这样:try/catch代码块,只要异常被catch捕捉到,后续的代码是可以照常执行的;如果不能被捕捉到,后续代码块是不能执行的。
那么,我们把根据properties文件获得连接 、和释放资源单独拿出来,作为工具方法Tools
建立一个工具类JDBCTools,所有方法为静态
那么在执行sql语句时就简单多了:
在编写jdbc程序时,要时刻注意用ty/catch/fianlly将连接资源的释放
当然也可以把update方法,写到JDBCTools中。
方法next()查看其是否有下一列数据,并将指针下移一列。resultSet的指针初始指向数据的第一列之前,所以要先resultSet.next()之后才可以读数据。
从中获取值得方法有两种,一种是依据类型和列标号,resultSet.getInt(1);标号是从1开始得,根据“SELECT 字段 FROM”中的字段进行定位。
一种是根据类型和列的字段名称:如resultSet.getDate("Birth");返回值类型是Date型
ResultSet使用完毕之后要关闭。
当然也可以重载JDBCTools的release方法,一次关闭连接、statement、resultSet资源。如果将返回的数据全部显示,可以while(resultSet.next())
其实,一个数据库的表,就可以看作为一个类型,表中的每一行数据都可以作为一个对象看待,所以,向一个表中输入数据,就先构造该表的一个类,用该类的对象,进行信息的输入。
数据库中的examstudent表,有字段:FlowId, Type, IdCard, ExamCard, StudentName, Location, Grade,
构造一个Student类型
不过可以看出,在这个过程中,使用sql的拼写方式,对于大型数据库的操作,非常费力,而且容器出错,所以就引入了PreparedStatement这个类。
PreparedStatement是Statement的一个子接口,也是在建立连接之后,通过连接对象获得。
执行增删改的方法都是,通过对象的executeUpdate方法。
不过,PreparedStatement是在获取对象时,传入具有占位符类型的sql语句。"INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)",在执行executeUpdate方法时,不需要再传入sql语句;
而Statement在获取对象是,不需要传入sql,而是在执行executeUpdate方法时,传入sql语句。
sql注入需要对sql查询语言非常熟悉,才能使用sql注入方法。
例如:数据库中有信息username:yuchen和password:123456
那么正常验证该用户的查询语句为:SELECT * FROM users WHERE username = 'yuchen' AND passoword = '123456';
那么使用sql注入:SELECT * FROM users WHERE username = 'a ' OR password = ' AND password =' OR '1'='1';
其实就是用户名为a ' OR password = 和密码为 OR '1'='1
Statement 不能防止sql注入,因为sql语句是字符串与参数拼写起来的,而PreparedStatement可以防止sql注入,因为sql是占位符 以及 对象传参。
使用Statement
而使用PreparedStatement:
利用sql语言的的别名特性,查询的列都配一个别名,该别名与类型的成员变量的名称相同
利用反射,构建类型对象,并利用反射为该类型的对象赋值。
列的个数columnCount= meta.getColumnCount();
列名columnLabel=meta.getColumnLabel(columnCount);
然后利用列的别名在ResultSet的结果集中得到相应的对象值
resultSet.getObject(columnCount);
field.setAccessible(true);设置私有变量也可以被访问
field.set(ob, value);ob是对象,value是要设置变量的值
Customer [id=7, name=yuchen, email=yuchen@cqupt.com, birth=1990-12-12]
Student [flowId=3, type=3, idCard=112233445566, exameCard=1212, studentName=yuchen, location=BeiJing, grade=98]
如果查询不到数据,那么返回的对象应该是null
commons-beanutils-1.9.2.jar 需要与 commons-logging-1.2.jar 包搭配使用
功能是给对象的属性进行赋值,以及获得对象的特定属性值。
对象的属性:是以set 和 get方法定义的,一个类有方法setXXX 和 getXXX方法,那么该对象就有xXX属性
对象的字段:把对象的成员变量称作为字段,而非属性。
是以BeanUtils的SetProperty和GetProperty 可以为对象属性赋值、获得对象属性
而当set 和 get 方法改为这样时:
BeanUtils.setProperty(stu, "idCard3", "2233445566");
String value = BeanUtils.getProperty(stu, "idCard2");
一般情况下,属性名和字段名保持一致。
那么,在根据数据库信息创建对象的时候,就可以是以BeanUtils工具了
DAO设计的好处在于,使该类型专注于数据库的操作,无关业务流程,是数据库操作独立成模块。
设计一个DAO接口类,可以对数据库进行update更新、get获取对象、getForList获取多个对象、getValue获取特定值的操作
创建一个测试类:
数据库元数据DatabaseMetaData,是对数据库连接进行描述的对象,里边包含了数据库连接的信息。如:数据库的类型、版本号、当前连接的用户名、数据库的名称、链接地址等:
数据库类别:MySQL
版本号:5
用户名:root@localhost
数据连接地址:jdbc:mysql://localhost:3306/cqupt
数据库:
bbs
cqupt
information_schema
jeecmsv6
mysql
phpmyadmin
phpmywind_db
test
zblog
通过conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)的重载方法,和preparedStatement.getGeneratedKeys()获取,返回为ResultSet类型。
通过resultSet.getObject(1)就可得到主键值。
mysql的blob有四种类型,可存储的大小不同:TinyBlob(255字节)、Blob(65k)、MediumBlob(16M)、LongBlob(4G)
JDBC对于Blob两种操作,读、写
向数据库写入Blob,使用PreparedStatement的setBlob(Int, InputStream)方法,写入一张图片:
从数据库读出Blob,使用PreparedStatement的getBlob(int)的方法,读出Blob对象,再利用InputStream向输出流OutputStream写入。
mysql数据库中的表有多种类型:MyISAM(不支持事务,适合查询量大的数据表)、InnoDB(支持事务,适合插入、更新量大的数据表),使用事务时需注意表是否为InnoDB类型。
JDBC中,事务的就是通过Connection连接完成的,一个事务操作只能与一个Connection有关,通过Connection完成。
三个步骤:
1、禁止自动提交,Connection类中的setAutoCommit(bool )完成
2、提交,在一组操作的最后,由commit()完成
3、回滚,事物中出现异常,使用回滚rollback()完成回到原始状态。
脏读: 事务1读取了事务2的还未提交的数据,也就是事务2的commit操作还未完成,有可能回滚,那么事务1读到的数据就是数据库不存在的。
不可重复读:事务1读取了数据,事务2紧接着对数据进行了处理,那么数据1再次读取数据,就会和上次不一样。
幻读:事务1读取了数据,事务2紧接着增加了几个字段,事务1再次读,就会读出和上次不一样的数据字段。
其中,脏读是绝不容忍出现的,不可重复读和幻读都是可以接受的。
为了应对上述情况,mysql和oracle都给出了隔离级别,其中:
oracle只有两种级别:READ COMMITTED(读已提交) 和 SERIALIZABLE(串行化),读已提交就是等待别的事务提交之后才可以读,处理过程中但未提交是不可以被别的事务读的。串行化是最严格的,隔离级别最高,但是性能十分低下。
mysql有四种隔离级别:READ UNCOMMITTED(读未提交)、 READ COMMITTED(读已提交),REPETABLE READ(可重复读)和SERIALIZBLE(串行化)。
读未提交是隔离级别最低的,会出现脏读、不可重复读、幻读的情况,一般不适用。、
读已提交,同oracle,是最经常使用的,避免了脏读,会出现不可重复读和幻读,但是可以接受。
可重复读,事务处理过程中,禁止别的事务对该字段进行更新,避免了脏读和不可重复读。
最经常使用的就是READ COMMITTED,mysql默认隔离级别是READ REPETABLE
通过Connection的setTransactionIsolation(int)可以设置隔离级别
参数是Connection的静态变量:
Connection.Transaction_READ_UNCOMMITED 读未提交
Connection.Transaction_READ_COMMITED 读已提交
Connection.Transaction_READ_REPETABLE 可重复读
Connection.Transaction_SERIALIZABLE 串行化
查询默认隔离级别(sql语句):SELECT @@tx_isolation
设置mysql的默认隔离级别(sql语句):
设置当前连接set transaction isolation level READ COMMITTED
设置数据库系统的全局事务隔离级别:set global transcation isolation level READ COMMITTED
主要使用PreparedStatement类中的addBatch()方法、executeBatch() 和 clearBatch()方法。
通过积攒单一操作一定数目之后,一次性执行,然后清空积攒的操作。
1、频繁的连接与断开会消耗服务器资源,特别是连接数量比较多时,会造成服务器的崩溃。
2、数据库连接如果忘记断开的话,会一直占用着服务器资源
3、如果不限制连接数目的上限的话,也会导致服务器崩溃
基于对数据库服务器的保护以及资源的高效利用,我们在开发过程中,通常都是通过数据库连接池完成与数据库的连接,而不是直接使用DriverManager的方法代码。
数据库连接池的思想是建立一个缓冲池,用以维护数据库连接,预先存放一定数量的连接,客户端获得连接在池子里得到,用完之后再放回池子中。
允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
JDBC中,Java为数据库连接池定义了一个接口javax.sql.DateSource,连接池工具通过实现这个接口来实现连接池功能,应用程序通过调用DateSource的方法来使用数据库连接池工具。
通常使用的Java数据的数据库连接池工具有两个:DBCP 和 C3P0
需要两个jar包:commons-pool2-2.4.2.jar,commons-dbcp2-2.1.1.jar
BasicDataSource是实现DataSource接口的实现类,下边看如何使用BasicDataSource配置连接池,并分配连接
导入两个jar包:c3p0-0.9.5.1.jar
mchange-commons-java-0.2.10.jar
c3p0中实现DataSource的类是ComboPooledDataSource,可以看如何通过该类配置连接池
那么JDBCTools中的获取连接的方法getConnection在实际开发中就是使用:
实际上就可以吧DBUtils看作是DAO编程的思想产物。
使用DBUtils,从类型QueryRunner入手,其中含有update方法可以实现增删改操作,query方法可以实现查询操作
使用update方法:
通过传递不同的ResultSetHandler接口的实现类给rst参数,可以实现不同的查询效果:查询一行返回一个对象、查询多行返回对象链表、查询特定值
将自己的实现类传递:
定义接口DAO--->实现类DAOImpl--->适用于特定表(类)的CustomerDAO--->程序开发中使用CustomerDAO对象操作数据库
定义DAO接口
大家都知道,有各种数据库可以使用,如mysql、oracle、DB2等,那么在程序开发过程中,如何保证程序与数据库的独立性,以及降低访问数据库的操作繁杂性,引入了JDBC(Java Data Base Connectivity),相当于一种介质,负责java程序与数据库的沟通。JDBC实际上提供了一套标准,一套接口,各数据厂商安装这个标准去开发自己的数据库软件,从而隐藏了各数据库软件底层的差异,使用者只需调用这些标准的接口,就能完成一系列对数据库的操作。
1、Driver接口与DriverManager
Driver接口
Driver接口是JDBC这套标准接口中,最基本的接口,每个数据库厂商都必须实现这个接口以及其提供的方法。例如mysql数据库的该接口的实现类就是com.mysql.jdbc.DriverDriver接口中有connect(String url, Properties info)方法,负责与数据连接,并返回相应的连接Connection类对象。
@Test public void test() throws SQLException { //创建Driver接口的实现类对象 Driver driver = new com.mysql.jdbc.Driver(); String jdbcUrl= "jdbc:mysql://localhost:3306/test"; Properties prop = new Properties(); prop.put("user", "root"); prop.put("password", "root"); //利用connect方法连接数据库 Connection connection = driver.connect(jdbcUrl, prop); System.out.println(connection); }可以看出,连接数据库的四要素:Driver的实现类,数据库访问url,用户user和密码password
数据库url的格式:
jdbc:mysql://localhost:3306/test
协议:子协议://地址:端口号/数据库的表
但是直接使用Driver接口,必须要与其实现类关联,可以使用配置文件降低耦合,在配置文件中写入Driver的实现类的全类名,在程序中使用反射获得实现类对象。
public Connection getConnection() throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{ String url = null; String user = null; String password = null; String driverClass = null; //将配置文件读入到输入流InputStream中 InputStream in= getClass().getClassLoader().getResourceAsStream("jdbc.properties"); Properties prop = new Properties(); //利用load方法加载输入流 prop.load(in); url = prop.getProperty("url"); driverClass = prop.getProperty("driver"); user = prop.getProperty("user"); password = prop.getProperty("password"); Properties info = new Properties(); info.put("user", user); info.put("password", password); //使用反射,根据Driver的全类名获得实现类 Driver driver = (Driver) Class.forName(driverClass).newInstance(); Connection connection = driver.connect(url, info); return connection; }jdbc.properities文件
driver = com.mysql.jdbc.Driver user = root password = root url = jdbc:mysql://localhost:3306/test
所以为了降低耦合,以及更方便的与数据库相连,一般使用DriverManager访问数据库,DriverManager的底层实现就是依赖Driver接口的实现,Driver接口一般不直接使用。
DriverManager
DriverManager可以注册多个Driver的实现类,即不同的数据库驱动,从而可以根据url来自动区分应该使用哪一个数据库的驱动类@Test public void testDriverManager() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException{ String url="jdbc:mysql://localhost:3306/test"; String url2="jdbc:oracle:thin:@localhost:1521:orcl"; String user="root"; String password = "root"; String driverClass = "com.mysql.jdbc.Driver"; String driverClass2="oracle.jdbc.driver.OracleDriver"; //加载驱动(注册驱动) //DriverManager.registerDriver((Driver) Class.forName(driverClass).newInstance()); //Driver的实现类里 有静态代码块,将驱动实例注册到DriverManager //同时加载两种数据库驱动 Class.forName(driverClass); Class.forName(driverClass2); //可以根据url的不同,自动选择驱动类型 Connection connection = DriverManager.getConnection(url, user, password); System.out.println("DriverManager :"+connection); }使用DriverManager连接数据库的步骤:
1、在配置文件中配置四要素:驱动全类名、数据库url、user、password,并在程序中利用Properties类获取到
2、使用反射加载数据库驱动 :Class.forName或者DriverManager的registerDriver方法
3、使用DriverManager的getConnection方法获取连接
public Connection getConnection2() throws IOException, ClassNotFoundException, SQLException{ //1.获取四要素 String url = null; String user = null; String password = null; String driverClass = null; InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties"); Properties prop = new Properties(); prop.load(in); url = prop.getProperty("url"); user = prop.getProperty("user"); password = prop.getProperty("password"); driverClass = prop.getProperty("driver"); //2.注册驱动 Class.forName(driverClass); //3.获得连接 Connection connection = DriverManager.getConnection(url, user, password); return connection; }
注意:mysql与oracle的url和驱动实现类的区别
mysql:
url jdbc:mysql://localhost:3306/test (默认可写为 jdbc:mysql:///test)
全类名: com.mysql.jdbc.Driver
oracle:
url jdbc:oracle:thin:@localhost:1521:orcl(待检验)
全类名: oracle.jdbc.driver.OracleDriver
2、Statement 执行sql语句
利用DriverManager获取连接之后,就需要Statement来执行我们需要的sql语句了,执行sql语句的过程
大概分为四步:1、建立连接、关闭连接(建立之后顺手关闭,中间再插入代码,为了不忘记关闭,因为建立的连接以及Statement都是应用程序与数据库连接的资源,要及时释放)
2、建立Statement,释放statement
3、准备sql语句
4、执行sql语句
@Test public void testStatement() throws ClassNotFoundException, IOException, SQLException{ Connection con=null; Statement state = null; try { //1.1获取连接 con = getConnection2(); //2.构建sql语句 String sql=null; sql="INSERT customers(NAME,EMAIL,BIRTH) VALUES('yuchen','yuchen@cqupt.com','1990-12-12' )"; // sql= "DELETE FROM customers WHERE id=2;"; // sql="UPDATE customers SET NAME='yy', EMAIL= 'yy@cqupt.com', BIRTH='1992-11-12' WHERE id=3"; //3.1 获取Statement state = con.createStatement(); //4. 执行sql语句,通过executeUpdata执行的语句只能是 INSERT,DELETE,UPDATA,不能使SELECT state.executeUpdate(sql); } catch (Exception e) { e.printStackTrace(); }finally{ try { if(state!=null) //3.2关闭statement state.close(); } catch (Exception e1) { e1.printStackTrace(); }finally{ if(con!=null) //1.2 关闭连接 con.close(); } } }为了确保connection 与 statement都能关闭,即使在出现异常时,所以最后使用嵌套的try/catch/finally语句
也可以是这样:try/catch代码块,只要异常被catch捕捉到,后续的代码是可以照常执行的;如果不能被捕捉到,后续代码块是不能执行的。
}finally{ if(con != null){ try{ con.close(); }catch(Exception ex){ ex.printStackTrace(); } } //因为如果con.close()发生异常,被后续catch捕获,catch代码块执行完毕会接着执行state的关闭 if(state != null){ try{ state.close(); }catch (Exception ex){ ex.printStackTrace(); } } }
工具类的建立与使用
其实可已看出,上述代码单不美观,而且繁杂,仔细分析,我们在执行sql语句的过程可以分为几个部分:获取连接,获取statement,执行sql,释放资源那么,我们把根据properties文件获得连接 、和释放资源单独拿出来,作为工具方法Tools
建立一个工具类JDBCTools,所有方法为静态
public class JDBCTools { public static void release(Statement statement, Connection conn){ if(statement != null){ try{ statement.close(); }catch (Exception e){ e.printStackTrace(); } } if(conn != null){ try{ conn.close(); }catch(Exception e){ e.printStackTrace(); } } } public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException{ //1.获取四要素 String url = null; String user = null; String password = null; String driverClass = null; InputStream in = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");//注意静态方法中的使用 Properties prop = new Properties(); prop.load(in); url = prop.getProperty("url"); user = prop.getProperty("user"); password = prop.getProperty("password"); driverClass = prop.getProperty("driver"); //2.注册驱动 Class.forName(driverClass); //3.获得连接 Connection connection = DriverManager.getConnection(url, user, password); return connection; } }
那么在执行sql语句时就简单多了:
public void update(String sql){ Connection conn=null; Statement statement=null; try { //获得连接 conn = JDBCTools.getConnection(); //获取statement statement = conn.createStatement(); //执行sql statement.executeUpdate(sql); } catch (ClassNotFoundException | IOException | SQLException e) { e.printStackTrace(); } finally{ //释放资源 JDBCTools.release(statement, conn); } } @Test public void testStatement() throws ClassNotFoundException, IOException, SQLException{ String sql = "DELETE FROM customers WHERE id =3"; update(sql); }
在编写jdbc程序时,要时刻注意用ty/catch/fianlly将连接资源的释放
当然也可以把update方法,写到JDBCTools中。
3、结果集ResultSet
结果集ResultSet是执行查询语句,将查询到的结果保存的一个类。方法next()查看其是否有下一列数据,并将指针下移一列。resultSet的指针初始指向数据的第一列之前,所以要先resultSet.next()之后才可以读数据。
从中获取值得方法有两种,一种是依据类型和列标号,resultSet.getInt(1);标号是从1开始得,根据“SELECT 字段 FROM”中的字段进行定位。
一种是根据类型和列的字段名称:如resultSet.getDate("Birth");返回值类型是Date型
ResultSet使用完毕之后要关闭。
public class JDBCTest { @Test public void testResultSet() { Connection conn =null; Statement statement = null; ResultSet resultSet = null; try { conn = getConnection(); statement = conn.createStatement(); String sql= "SELECT id, name, email,birth FROM customers "; resultSet = statement.executeQuery(sql); if(resultSet.next()){ // String name = resultSet.getString("NAME"); String name = resultSet.getString(2); // String email = resultSet.getString("EMAIL"); String email = resultSet.getString(3); Date date = resultSet.getDate("BIRTH"); // Date date = resultSet.getDate(4); System.out.println("name :"+name+" email:"+email+" birth:"+date); } } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IOException | SQLException e) { e.printStackTrace(); } finally{ try { //关闭结果集 resultSet.close(); } catch (SQLException e) { e.printStackTrace(); }finally{ JDBCTools.release(statement, conn); } } }
当然也可以重载JDBCTools的release方法,一次关闭连接、statement、resultSet资源。如果将返回的数据全部显示,可以while(resultSet.next())
4、以面向对象的方法向数据库中输入信息
我们经常说以面向对象的方法写程序,那么在向数据库中输入信息时,该如何利用面向对象的思想呢?其实,一个数据库的表,就可以看作为一个类型,表中的每一行数据都可以作为一个对象看待,所以,向一个表中输入数据,就先构造该表的一个类,用该类的对象,进行信息的输入。
数据库中的examstudent表,有字段:FlowId, Type, IdCard, ExamCard, StudentName, Location, Grade,
构造一个Student类型
public class Student { @Override public String toString() { return "Student [flowId=" + flowId + ", type=" + type + ", idCard=" + idCard + ", exameCard=" + exameCard + ", studentName=" + studentName + ", location=" + location + ", grade=" + grade + "]"; } public int getFlowId() { return flowId; } public void setFlowId(int flowId) { this.flowId = flowId; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } public String getExameCard() { return exameCard; } public void setExameCard(String exameCard) { this.exameCard = exameCard; } public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public int getGrade() { return grade; } public void setGrade(int grade) { this.grade = grade; } private int flowId; private int type; private String idCard; private String exameCard; private String studentName; private String location; private int grade; }利用控制台的Scanner类,进行数据的输入,并赋值到Student类的对象student中
public Student getStudentFromConsole(){ Scanner scanner = new Scanner(System.in); Student student = new Student(); System.out.print("Please input FlowId:"); student.setFlowId(scanner.nextInt()); System.out.print("Please input Type:"); student.setType(scanner.nextInt()); System.out.print("Please input IdCard:"); student.setIdCard(scanner.next()); System.out.print("Please input ExameCard:"); student.setExameCard(scanner.next()); System.out.print("Please input StudentName:"); student.setStudentName(scanner.next()); System.out.print("Please input Location:"); student.setLocation(scanner.next()); System.out.print("Please input Grade:"); student.setGrade(scanner.nextInt()); return student; }利用student进行对数据库信息的插入
public void addNewStudent(Student student){ // String sql="INSERT INTO examstudent VALUES(1,2,'123456','1214','Yuchen','BeiJing',98)"; String sql = "INSERT INTO examstudent" +" VALUES("+student.getFlowId() +","+student.getType() +",'"+student.getIdCard() +"','"+student.getExameCard() +"','"+student.getStudentName() +"','"+student.getLocation() +"',"+student.getGrade()+")";//这种sql拼写的方式非常费力,且易出错 System.out.println(sql); JDBCTools.update(sql); }那么在主程序中就调用即可:
@Test public void testAddNewStudent(){ Student student = getStudentFromConsole(); addNewStudent(student); }这样就完成了数据的插入。
不过可以看出,在这个过程中,使用sql的拼写方式,对于大型数据库的操作,非常费力,而且容器出错,所以就引入了PreparedStatement这个类。
5、PreparedStatement
PreparedStatement
的使用好处,其一就是避免了拼写sql语句带来的麻烦,避免产生错误;其二就是可以避免由于拼接sql产生的sql注入安全问题。PreparedStatement是Statement的一个子接口,也是在建立连接之后,通过连接对象获得。
执行增删改的方法都是,通过对象的executeUpdate方法。
不过,PreparedStatement是在获取对象时,传入具有占位符类型的sql语句。"INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)",在执行executeUpdate方法时,不需要再传入sql语句;
而Statement在获取对象是,不需要传入sql,而是在执行executeUpdate方法时,传入sql语句。
@Test public void testAddNewStudent(){ Student student = getStudentFromConsole(); addNewStudent(student); }为了抽离出PreparedStatement的使用,对于所有数据库的操作都适合,建立一个工具类方法update
public void addNewStudent(Student student){
String sql= "INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)";
Connection conn=null;
PreparedStatement preparedStatement = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, student.getFlowId());
preparedStatement.setInt(2, student.getFlowId());
preparedStatement.setString(3, student.getIdCard());
preparedStatement.setString(4, student.getExameCard());
preparedStatement.setString(5, student.getStudentName());
preparedStatement.setString(6, student.getLocation());
preparedStatement.setInt(7, student.getGrade());
preparedStatement.executeUpdate();
} catch (ClassNotFoundException | IOException | SQLException e) {
e.printStackTrace();
}finally{
JDBCTools.release(preparedStatement, conn);
}
}
public static void updata(String sql, Object ... args){//可变形参使用 Connection conn = null; PreparedStatement preparedStatement = null; try { conn = getConnection(); preparedStatement = conn.prepareStatement(sql); for(int i=0; i<args.length;i++){ preparedStatement.setObject(i+1, args[i]); } preparedStatement.executeUpdate(); } catch (ClassNotFoundException | IOException | SQLException e) { e.printStackTrace(); } finally{ release(null,preparedStatement, conn); } }那么在主程序:
@Test public void testAddNewStudent(){ Student student = getStudentFromConsole(); addNewStudent(student); }这样基于PreparedStatement的update方法就可以被其他程序使用了。
public void addNewStudent(Student student){
String sql = "INSERT INTO examstudent(flowid, type, idcard, examcard, studentname, location, grade) "
+ "VALUES(?,?,?,?,?,?,?)";
JDBCTools.updata(sql, student.getFlowId(), student.getType(), student.getIdCard(),
student.getExameCard(), student.getStudentName(), student.getLocation(), student.getGrade());
}
SQL注入
SQL注入是什么呢?其实就是利用sql查询语言的局限,通过一些手段,使得不通过正常的信息就可以得到数据库中想要的数据。sql注入需要对sql查询语言非常熟悉,才能使用sql注入方法。
例如:数据库中有信息username:yuchen和password:123456
那么正常验证该用户的查询语句为:SELECT * FROM users WHERE username = 'yuchen' AND passoword = '123456';
那么使用sql注入:SELECT * FROM users WHERE username = 'a ' OR password = ' AND password =' OR '1'='1';
其实就是用户名为a ' OR password = 和密码为 OR '1'='1
Statement 不能防止sql注入,因为sql语句是字符串与参数拼写起来的,而PreparedStatement可以防止sql注入,因为sql是占位符 以及 对象传参。
使用Statement
@Test public void testSQLInject(){ String userName = null; String password = null; // userName = "yuchen"; // password = "123456"; userName = "a 'OR PASSWORD =" ; password = "OR '1'='1"; String sql = "SELECT * FROM users WHERE username = '"+userName+"' and password = '"+password+"'"; Connection conn = null; Statement state = null; ResultSet result = null; try{ conn = JDBCTools.getConnection(); state = conn.createStatement(); result = state.executeQuery(sql); if(result.next()){ System.out.println("登录成功!"); }else{ System.out.println("用户或密码错误!"); } }catch (Exception e){ e.printStackTrace(); } finally{ JDBCTools.release(result,state, conn); } }输出结果为 登陆成功!
而使用PreparedStatement:
@Test public void testSQLInject2(){ String userName = null; String password = null; // userName = "yuchen"; // password = "123456"; userName = "a 'OR PASSWORD =" ; password = "OR '1'='1"; String sql = "SELECT * FROM users WHERE username = ? and password = ?"; Connection conn = null; PreparedStatement preparedState = null; ResultSet result = null; try{ conn = JDBCTools.getConnection(); preparedState = conn.prepareStatement(sql); preparedState.setString(1, userName); preparedState.setString(2, password); result = preparedState.executeQuery(); if(result.next()){ System.out.println("登陆成功!"); }else{ System.out.println("用户或密码错误!"); } }catch(Exception e){ e.printStackTrace(); } finally{ JDBCTools.release(result, preparedState, conn); } }输出结果 用户或密码错误!
6、以面向对象的方式从数据库查询信息
面向对象的方式从数据库获取信息,即从数据库查询操作,得到的信息以类对象的形式展示利用sql语言的的别名特性,查询的列都配一个别名,该别名与类型的成员变量的名称相同
利用反射,构建类型对象,并利用反射为该类型的对象赋值。
ResultSetMetaData
使用结果集元数据类型对象ResultSetMetaData meta=resultSet.getMetaData(),获得查询操作结果集的一些信息:列的个数columnCount= meta.getColumnCount();
列名columnLabel=meta.getColumnLabel(columnCount);
然后利用列的别名在ResultSet的结果集中得到相应的对象值
resultSet.getObject(columnCount);
利用反射为对象成员变量赋
Field field = clazz.getDeclaredField(name);name是成员变量的名称field.setAccessible(true);设置私有变量也可以被访问
field.set(ob, value);ob是对象,value是要设置变量的值
@Test public void testGetCustomer() throws Exception{ String sql = "SELECT id id, name name, email email, birth birth FROM customers WHERE id=?"; int id = 7; Customer customer = (Customer) getObject(Customer.class,sql, id); System.out.println(customer); String sql2 = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent " + "WHERE flowid =?"; // String sql2 = "SELECT flowId, type, idCard, examCard, studentName, location, grade FROM examstudent " // + "WHERE flowid =?"; int flowId = 3; Student stu = (Student) getObject(Student.class, sql2, flowId); System.out.println(stu); } public <T> Object getObject(Class<T> clazz, String sql, Object ... args) throws InstantiationException, IllegalAccessException{ Object ob=null; Connection conn = null; PreparedStatement preparedState = null; ResultSet result = null; try{ conn = JDBCTools.getConnection(); preparedState = conn.prepareStatement(sql); for(int i =0; i<args.length; i++){ preparedState.setObject(i+1,args[i]); } result = preparedState.executeQuery(); //获得结果集元数据对象 ResultSetMetaData meta = result.getMetaData(); //判读是否有相应的数据,有创建类型对象;无,返回的是null if(result.next()){ ob=clazz.newInstance(); for(int column=0; column < meta.getColumnCount();column++){ //从结果集元数据中提取出列的别名、和对应的值 String columnLabel = meta.getColumnLabel(column+1); Object value = result.getObject(columnLabel); //利用反射为类型对象赋值,此处可以使用BeanUtils工具包(是通用正宗做法) Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(ob, value); } } } catch(Exception e){ e.printStackTrace(); } finally{ JDBCTools.release(result, preparedState, conn); } return ob; }输出结果;
Customer [id=7, name=yuchen, email=yuchen@cqupt.com, birth=1990-12-12]
Student [flowId=3, type=3, idCard=112233445566, exameCard=1212, studentName=yuchen, location=BeiJing, grade=98]
如果查询不到数据,那么返回的对象应该是null
BeanUtils的使用:
BeanUtils是Apache开源组织的一个java开源工具包,需要在http://commons.apache.org/下载commons-beanutils-1.9.2.jar 需要与 commons-logging-1.2.jar 包搭配使用
功能是给对象的属性进行赋值,以及获得对象的特定属性值。
对象的属性:是以set 和 get方法定义的,一个类有方法setXXX 和 getXXX方法,那么该对象就有xXX属性
对象的字段:把对象的成员变量称作为字段,而非属性。
public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; }上述代码有set 和 get 方法,故,该类型对象有idCard属性
是以BeanUtils的SetProperty和GetProperty 可以为对象属性赋值、获得对象属性
@Test public void GetPropertyTest() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{ Student stu = new Student(); System.out.println(stu); //结果:Student [flowId=0, type=0, idCard=null, exameCard=null, studentName=null, location=null, grade=0] //使用BeanUtils工具为对象属性赋值 BeanUtils.setProperty(stu, "idCard", "2233445566"); System.out.println(stu); //结果:Student [flowId=0, type=0, idCard=2233445566, exameCard=null, studentName=null, location=null, grade=0] String value = BeanUtils.getProperty(stu, "idCard"); System.out.println(value); }
而当set 和 get 方法改为这样时:
public String getIdCard2() { return idCard; } public void setIdCard3(String idCard) { this.idCard = idCard; }SetProperty 和 GetProperty就需要改为:
BeanUtils.setProperty(stu, "idCard3", "2233445566");
String value = BeanUtils.getProperty(stu, "idCard2");
一般情况下,属性名和字段名保持一致。
那么,在根据数据库信息创建对象的时候,就可以是以BeanUtils工具了
public <T> Object getObject(Class<T> clazz, String sql, Object ... args) throws InstantiationException, IllegalAccessException{ Object ob=null; Connection conn = null; PreparedStatement preparedState = null; ResultSet result = null; try{ conn = JDBCTools.getConnection(); preparedState = conn.prepareStatement(sql); for(int i =0; i<args.length; i++){ preparedState.setObject(i+1,args[i]); } result = preparedState.executeQuery(); //获得结果集元数据对象 ResultSetMetaData meta = result.getMetaData(); //判读是否有相应的数据,有创建类型对象;无,返回的是null if(result.next()){ ob=clazz.newInstance(); for(int column=0; column < meta.getColumnCount();column++){ //从结果集元数据中提取出列的别名、和对应的值 String columnLabel = meta.getColumnLabel(column+1); Object value = result.getObject(columnLabel); //利用BeanUtils工具包为对象属性赋值 BeanUtils.setProperty(ob, columnLabel, value); } } } catch(Exception e){ e.printStackTrace(); } finally{ JDBCTools.release(result, preparedState, conn); } return ob; }
7、DAO设计模式
DAO :Data Access Object 数据访问对象,即设计一个类型,用该类型的对象作为专门访问数据库的操作。DAO设计的好处在于,使该类型专注于数据库的操作,无关业务流程,是数据库操作独立成模块。
设计一个DAO接口类,可以对数据库进行update更新、get获取对象、getForList获取多个对象、getValue获取特定值的操作
public interface DAO { //更新操作 update :INSERT 、DELETE、 Update public void update(String sql, Object ... args); //查询操作一个 get : SELECT public <T> T get(Class<T> clazz, String sql, Object ... args); //查询一组 getForList :SELECT public <T> List<T> getForList(Class<T> clazz, String sql, Object ...args) ; //返回对象的属性值 getValue,或者某个统计值 public <E> E getValue(String sql, Object ... args); }实现类:
public class DAOImpl implements DAO { @Override public void update(String sql, Object... args) { Connection conn = null; PreparedStatement preparedStatement = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); for(int i = 0;i < args.length; i++){ preparedStatement.setObject(i+1, args[i]); } preparedStatement.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(null, preparedStatement, conn); } } @Override public <T> T get(Class<T> clazz, String sql, Object... args) { T entity = null; Connection conn = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); for(int i =0; i< args.length; i++){ preparedStatement.setObject(i+1, args[i]); } resultSet = preparedStatement.executeQuery(); ResultSetMetaData meta = resultSet.getMetaData(); if(resultSet.next()){ entity = clazz.newInstance(); for(int i=0;i<meta.getColumnCount(); i++){ String label = meta.getColumnLabel(i+1); Object value = resultSet.getObject(i+1); // System.out.println(label+" "+value); BeanUtils.setProperty(entity, label, value); } } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(resultSet, preparedStatement, conn); } return entity; } @Override public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) { List<T> list = new ArrayList(); T entity = null; Connection conn = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); for(int i =0; i< args.length; i++){ preparedStatement.setObject(i+1, args[i]); } resultSet = preparedStatement.executeQuery(); ResultSetMetaData meta = resultSet.getMetaData(); while(resultSet.next()){ entity = clazz.newInstance(); for(int i=0;i<meta.getColumnCount(); i++){ String label = meta.getColumnLabel(i+1); Object value = resultSet.getObject(i+1); BeanUtils.setProperty(entity, label, value); } list.add(entity); } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(resultSet, preparedStatement, conn); } return list; } @Override public <E> E getValue(String sql, Object... args) { E value = null; Connection conn = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); for(int i =0; i< args.length; i++){ preparedStatement.setObject(i+1, args[i]); } resultSet = preparedStatement.executeQuery(); if(resultSet.next()){ value = (E) resultSet.getObject(1); } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(resultSet, preparedStatement, conn); } return value; } }那么,在程序编写中,就可以使用DAO的对象进行对数据库的操作,简化了对数据库的操作
创建一个测试类:
public class DAOTest { @Test public void testUpdate() { String sql =null; sql = "INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)"; DAO dao = new DAOImpl(); dao.update(sql, 123,3, "12345677", "2013452","Musk", "China",99); } @Test public void testGet() { String sql =null; sql = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent " + "WHERE flowid =?"; DAO dao = new DAOImpl(); Student stu = dao.get(Student.class, sql, 123); System.out.println(stu); //输出:Student [flowId=123, type=3, idCard=12345677, exameCard=2013452, studentName=Musk, location=China, grade=99] } @Test public void testGetForList() { String sql =null; sql = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent " + "WHERE studentName = ?"; DAO dao = new DAOImpl(); List<Student> list = dao.getForList(Student.class, sql, "yuchen"); System.out.println(list); //输出:[Student [flowId=1, type=2, idCard=123456, exameCard=1214, studentName=Yuchen, location=BeiJing, grade=98], //Student [flowId=2, type=2, idCard=123456, exameCard=1214, studentName=Yuchen, location=BeiJing, grade=98], //Student [flowId=3, type=3, idCard=112233445566, exameCard=1212, studentName=yuchen, location=BeiJing, grade=98]] } @Test public void testGetValue() { String sql =null; sql = "SELECT examCard FROM examstudent " + "WHERE flowId = ?"; DAO dao = new DAOImpl(); String card = (String) dao.getValue(sql, 123); System.out.println(card); //输出:2013452 } }
8、DatabaseMetaData数据库元数据
前边学过了结果集的元数据ResultSetMetaData,里边包含了结果集的一些信息,列的数目、列名等。数据库元数据DatabaseMetaData,是对数据库连接进行描述的对象,里边包含了数据库连接的信息。如:数据库的类型、版本号、当前连接的用户名、数据库的名称、链接地址等:
@Test public void testDatabaseMetaData() { Connection conn = null; ResultSet resultSet =null; try { conn = JDBCTools.getConnection(); DatabaseMetaData meta = conn.getMetaData(); System.out.println("数据库类别:"+meta.getDatabaseProductName()); System.out.println("版本号:"+meta.getDatabaseMajorVersion()); System.out.println("用户名:"+meta.getUserName()); System.out.println("数据连接地址:"+meta.getURL()); resultSet = meta.getCatalogs(); System.out.println("数据库:"); while(resultSet.next()){ System.out.println(resultSet.getString(1)); } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(resultSet,null, conn); } }输出结果:
数据库类别:MySQL
版本号:5
用户名:root@localhost
数据连接地址:jdbc:mysql://localhost:3306/cqupt
数据库:
bbs
cqupt
information_schema
jeecmsv6
mysql
phpmyadmin
phpmywind_db
test
zblog
9、在插入数据时,获得主键值
向数据库中插入一组数据,自动生成的主键值该如何获取?通过conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)的重载方法,和preparedStatement.getGeneratedKeys()获取,返回为ResultSet类型。
通过resultSet.getObject(1)就可得到主键值。
@Test public void testGetKey() { Connection conn = null; PreparedStatement preparedStatement = null; ResultSet resultSet= null; String sql ="INSERT customers(name,email,birth) VALUES(?,?,?)"; try { conn= JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); preparedStatement.setString(1, "Musk"); preparedStatement.setString(2, "musk@163.com"); preparedStatement.setDate(3,new Date(new java.util.Date().getTime())); preparedStatement.executeUpdate(); //获得主键的结果集,结果集中只有一列resultSet.getObject(1) resultSet = preparedStatement.getGeneratedKeys(); if(resultSet.next()){ System.out.println(resultSet.getObject(1)); } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCTools.release(resultSet, preparedStatement, conn); } }
10、 Blob处理
Blob是一个类型,用于在数据库存储数据块时使用。mysql的blob有四种类型,可存储的大小不同:TinyBlob(255字节)、Blob(65k)、MediumBlob(16M)、LongBlob(4G)
JDBC对于Blob两种操作,读、写
向数据库写入Blob,使用PreparedStatement的setBlob(Int, InputStream)方法,写入一张图片:
public void testInsertBlob(){ String sql = "INSERT INTO customers(name, email, birth, picture) VALUES(?,?,?,?)"; Connection conn = null; PreparedStatement preparedStatement = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); preparedStatement.setObject(1, "yuchen00"); preparedStatement.setObject(2,"yuchen00@163.com"); preparedStatement.setObject(3, "1990-1-1"); InputStream in = new FileInputStream("卡通.jpg"); preparedStatement.setBlob(4, in); preparedStatement.executeUpdate(); } catch (ClassNotFoundException | IOException | SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ JDBCTools.release(preparedStatement, conn); } }“卡通.jpg”图片在项目的根目录下。
从数据库读出Blob,使用PreparedStatement的getBlob(int)的方法,读出Blob对象,再利用InputStream向输出流OutputStream写入。
@Test public void testReadBlob(){ String sql = "SELECT name, email, birth , picture FROM customers WHERE id = ?"; Connection conn = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { conn = JDBCTools.getConnection(); preparedStatement = conn.prepareStatement(sql); preparedStatement.setInt(1, 19); resultSet = preparedStatement.executeQuery(); if(resultSet.next()){ String name = resultSet.getString(1); String email = resultSet.getString(2); Date birth = resultSet.getDate(3); System.out.println(name+" "+ email+" " + birth+ " "); Blob blob = resultSet.getBlob(4); InputStream in = blob.getBinaryStream(); OutputStream out = new FileOutputStream("cartoon.jpg"); //写入到文件的方法 byte[] buffer = new byte[1024]; int length= 0; while((length = in.read(buffer))!=-1){ out.write(buffer, 0, length); } out.close(); in.close(); } } catch (ClassNotFoundException | IOException | SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ JDBCTools.release(resultSet,preparedStatement, conn); } }
11、事务处理
事务操作
所谓事务,是指一组逻辑操作单元,使数据从一种状态变为另种状态。事务由一些列的操作组成,操作过程中保持:原子性、一致性、隔离性和持久性。mysql数据库中的表有多种类型:MyISAM(不支持事务,适合查询量大的数据表)、InnoDB(支持事务,适合插入、更新量大的数据表),使用事务时需注意表是否为InnoDB类型。
JDBC中,事务的就是通过Connection连接完成的,一个事务操作只能与一个Connection有关,通过Connection完成。
三个步骤:
1、禁止自动提交,Connection类中的setAutoCommit(bool )完成
2、提交,在一组操作的最后,由commit()完成
3、回滚,事物中出现异常,使用回滚rollback()完成回到原始状态。
@Test public void testTransAction() { Connection conn = null; try{ conn = JDBCTools.getConnection(); //禁止自动提交,设置自动提交为false conn.setAutoCommit(false); String sql= "UPDATE users SET balance = balance+500 WHERE username = 'yuchen' "; update(conn, sql); int a =10/0; sql= "UPDATE users SET balance = balance-500 WHERE username = 'jack'"; update(conn,sql); //提交 conn.commit(); } catch (Exception e){ e.printStackTrace(); try { //回滚操作 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally{ JDBCTools.release(null,null, conn); } } public void update(Connection conn, String sql, Object ... args){ PreparedStatement preparedStatement = null; try { preparedStatement = conn.prepareStatement(sql); for(int i = 0; i < args.length; i++ ){ preparedStatement.setObject(i+1, args[i]); } preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, preparedStatement, null); } }
事务的隔离级别
事务就像线程一样,会存在多个事务之间的互相影响问题。两个事务之间会产生的影响如下:脏读: 事务1读取了事务2的还未提交的数据,也就是事务2的commit操作还未完成,有可能回滚,那么事务1读到的数据就是数据库不存在的。
不可重复读:事务1读取了数据,事务2紧接着对数据进行了处理,那么数据1再次读取数据,就会和上次不一样。
幻读:事务1读取了数据,事务2紧接着增加了几个字段,事务1再次读,就会读出和上次不一样的数据字段。
其中,脏读是绝不容忍出现的,不可重复读和幻读都是可以接受的。
为了应对上述情况,mysql和oracle都给出了隔离级别,其中:
oracle只有两种级别:READ COMMITTED(读已提交) 和 SERIALIZABLE(串行化),读已提交就是等待别的事务提交之后才可以读,处理过程中但未提交是不可以被别的事务读的。串行化是最严格的,隔离级别最高,但是性能十分低下。
mysql有四种隔离级别:READ UNCOMMITTED(读未提交)、 READ COMMITTED(读已提交),REPETABLE READ(可重复读)和SERIALIZBLE(串行化)。
读未提交是隔离级别最低的,会出现脏读、不可重复读、幻读的情况,一般不适用。、
读已提交,同oracle,是最经常使用的,避免了脏读,会出现不可重复读和幻读,但是可以接受。
可重复读,事务处理过程中,禁止别的事务对该字段进行更新,避免了脏读和不可重复读。
最经常使用的就是READ COMMITTED,mysql默认隔离级别是READ REPETABLE
通过Connection的setTransactionIsolation(int)可以设置隔离级别
参数是Connection的静态变量:
Connection.Transaction_READ_UNCOMMITED 读未提交
Connection.Transaction_READ_COMMITED 读已提交
Connection.Transaction_READ_REPETABLE 可重复读
Connection.Transaction_SERIALIZABLE 串行化
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
查询默认隔离级别(sql语句):SELECT @@tx_isolation
设置mysql的默认隔离级别(sql语句):
设置当前连接set transaction isolation level READ COMMITTED
设置数据库系统的全局事务隔离级别:set global transcation isolation level READ COMMITTED
12、批量处理
当需要性插入大量数据时,是比较耗费时间的。利用批处理方法,一次性提交sql,执行多次插入,可以节省时间。主要使用PreparedStatement类中的addBatch()方法、executeBatch() 和 clearBatch()方法。
通过积攒单一操作一定数目之后,一次性执行,然后清空积攒的操作。
@Test public void testBatch(){ Connection conn = null; PreparedStatement preparedStatement = null; try{ String sql = "INSERT INTO customers VALUES(?,?,?,?)"; conn = JDBCTools.getConnection(); //开始事务 conn.setAutoCommit(false); preparedStatement = conn.prepareStatement(sql); long begin = System.currentTimeMillis(); Date date = new Date(new java.util.Date().getTime()); for(int i=0; i<100000; i++){ preparedStatement.setInt(1, i+1); preparedStatement.setString(2, "yuchen"+(i+1)); preparedStatement.setString(3, "yuchen"+(i+1)+"@163.com"); preparedStatement.setDate(4, date); //积攒 preparedStatement.addBatch(); //到达数目,处理积攒的batch if((i+1)%300 == 0){ preparedStatement.executeBatch(); preparedStatement.clearBatch(); } } //处理未完成的batch preparedStatement.executeBatch(); preparedStatement.clearBatch(); long end= System.currentTimeMillis(); System.out.println(end - begin); //提交事务 conn.commit(); } catch(Exception e) { try { //回滚事务 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally{ JDBCTools.release(preparedStatement, conn); } }
13、数据库连接池
前边的数据库连接都是直接通过DriverManager的getConnection方法获取的,需要向数据库服务器传输用户名与密码来的得到连接。Connection connection = DriverManager.getConnection(url, user, password);为什么使用数据库连接池呢?主要有以下几点
1、频繁的连接与断开会消耗服务器资源,特别是连接数量比较多时,会造成服务器的崩溃。
2、数据库连接如果忘记断开的话,会一直占用着服务器资源
3、如果不限制连接数目的上限的话,也会导致服务器崩溃
基于对数据库服务器的保护以及资源的高效利用,我们在开发过程中,通常都是通过数据库连接池完成与数据库的连接,而不是直接使用DriverManager的方法代码。
数据库连接池的思想是建立一个缓冲池,用以维护数据库连接,预先存放一定数量的连接,客户端获得连接在池子里得到,用完之后再放回池子中。
允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
JDBC中,Java为数据库连接池定义了一个接口javax.sql.DateSource,连接池工具通过实现这个接口来实现连接池功能,应用程序通过调用DateSource的方法来使用数据库连接池工具。
通常使用的Java数据的数据库连接池工具有两个:DBCP 和 C3P0
DBCP
dbcp是Apache下的一个数据库连接池工具,依赖另一个开源系统Commons-pool,Tomcat就是使用的DBCP需要两个jar包:commons-pool2-2.4.2.jar,commons-dbcp2-2.1.1.jar
BasicDataSource是实现DataSource接口的实现类,下边看如何使用BasicDataSource配置连接池,并分配连接
@Test public void testDBPC() throws SQLException{ BasicDataSource dataSource = null; Connection connection = null; //创建JDBC数据源实例 dataSource = new BasicDataSource(); //配置必须的属性 dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setUrl("jdbc:mysql://localhost:3306/cqupt"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); //配置可选属性 //1.设置初始连接数目 dataSource.setInitialSize(10); //2.设置数据库连接池中,同一时刻可以向数据库申请的最多的连接数 dataSource.setMaxTotal(50); //3.指定最少空闲连接数,指在数据连接池中保持空闲的连接数目 dataSource.setMinIdle(5); //4.等待数据库连接池分配连接的最长时间,超时抛出异常,单位毫秒 dataSource.setMaxWaitMillis(5000); //分配连接 connection = dataSource.getConnection(); System.out.println(connection); }上述可以看出bdcp的实现,实际在开发中只通过DataSource接口进行配置连接池,和分配连接,是通过BasicDataSourceFactory和配置文件配置的。
@Test public void testDBCPWithDataSourceFactory() throws Exception{ DataSource dataSource = null; //加载配置文件到Properties类对象 InputStream inStream = ConnectionPoolTest.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties properties = new Properties(); properties.load(inStream); //利用BasicDataSourceFactory创建数据源 dataSource = BasicDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); System.out.println(connection); }配置文件里的配置,键名是确定的,同javaBean,实际上是通过BasicDataSource的set方法设置的
username=root password=root driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/cqupt initialSize=10 maxTotal=50 minIdle=5 maxWaitMillis=5000
C3P0
是hibernate指定的数据库连接数工具,性能也很不错。导入两个jar包:c3p0-0.9.5.1.jar
mchange-commons-java-0.2.10.jar
c3p0中实现DataSource的类是ComboPooledDataSource,可以看如何通过该类配置连接池
@Test public void testC3p0() throws PropertyVetoException, SQLException{ ComboPooledDataSource cpds = new ComboPooledDataSource(); cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/cqupt" ); cpds.setUser("root"); cpds.setPassword("root"); Connection connection = cpds.getConnection(); System.out.println(connection); }同dbcp一样,在实际开发中也是使用配置文件的方式进行,系统默认的配置文件名为c2p0-config.xml,直接在创建ComboPooledDataSource时指定配置名称就可以(不是文件名)
@Test public void testC3p0() throws PropertyVetoException, SQLException{ DataSource cpds = new ComboPooledDataSource("helloc3p0"); Connection connection = cpds.getConnection(); System.out.println(connection); }c3p0-config.xml
<c3p0-config> <!--指定配置的名称,在程序中使用 --> <named-config name="helloc3p0"> <!-- 指定数据库连接的基本属性 --> <property name="user">root</property> <property name="password">root</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/cqupt</property> <!-- 配置一些可选属性 --> <!--连接不足时,一次性向服务器申请多少个连接 --> <property name="acquireIncrement">50</property> <!-- 初始化连接数 --> <property name="initialPoolSize">100</property> <!-- 最小的连接数 --> <property name="minPoolSize">50</property> <!-- 最大的连接数 --> <property name="maxPoolSize">1000</property> <!-- 数据库连接池可以维护的Statement的个数 --> <property name="maxStatements">20</property> <!-- 每个连接同时可以使用的Statement的个数 --> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
那么JDBCTools中的获取连接的方法getConnection在实际开发中就是使用:
private static DataSource dataSource; static { dataSource = new ComboPooledDataSource("helloc3p0"); } public static Connection getConnection() throws SQLException{ Connection connection = dataSource.getConnection(); return connection; }释放链接的方法不变,但是实际上的意义不同,数据连接池的Connection对象执行close操作,并不是真的关闭了连接,而是将连接归还到数据库连接池中
public static void release(ResultSet resultSet,Statement statement, Connection conn){ if(resultSet!=null){ try{ resultSet.close(); }catch (Exception e){ e.printStackTrace(); } } if(statement != null){ try{ statement.close(); }catch (Exception e){ e.printStackTrace(); } } if(conn != null){ try{ //数据连接池的Connection对象执行close操作,并不是真的关闭了连接, //而是将连接归还到数据库连接池中 conn.close(); }catch(Exception e){ e.printStackTrace(); } } }
14、DBUtils的使用
DBUtils是Apache的一款java开发工具,目的就是封装我们前边学习的一些底层操作,完成简单的调用就可以实现数据库的增删改查操作。实际上就可以吧DBUtils看作是DAO编程的思想产物。
使用DBUtils,从类型QueryRunner入手,其中含有update方法可以实现增删改操作,query方法可以实现查询操作
使用update方法:
/** * 使用QueryRunner的update(conn, sql, args)实现DELETE、INSERT、UPDATE操作 */ @Test public void testQueryRunnerUpdate(){ Connection conn= null; try { conn = JDBCTools.getConnection(); String sql = "INSERT INTO customers VALUES(?,?,?,?)"; QueryRunner queryRunner = new QueryRunner(); queryRunner.update(conn, sql, 1,"jack","jack@163.com","1991-12-12"); } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } }使用query方法,实际上有多个重载的方法,常用的是query(conn, sql, rst, args)
通过传递不同的ResultSetHandler接口的实现类给rst参数,可以实现不同的查询效果:查询一行返回一个对象、查询多行返回对象链表、查询特定值
将自己的实现类传递:
class MyResultSetHandler implements ResultSetHandler{ @Override public Object handle(ResultSet rs) throws SQLException { Customer customer = null; if(rs.next()){ customer = new Customer(rs.getInt(1),rs.getString(2),rs.getString(3), rs.getDate(4)); } return customer; } } /** * 调用QueryRunner的query(conn, sql, rst, args),将实现ResultSetHandler接口的自定义类传递给rst参数 * 实际上query方法返回的值,就是返回的接口ResultSetHandler的handler方法返回值 */ @Test public void testQueryRunnerQueryWithMyResultSetHandler(){ Connection conn = null; try { conn = JDBCTools.getConnection(); String sql = "SELECT id, name , email, birth FROM customers WHERE id =?"; QueryRunner queryRunner = new QueryRunner(); Customer customer = (Customer) queryRunner.query(conn, sql, new MyResultSetHandler(), 1); System.out.println(customer); //输出:Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12] } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } }传递DBUtils系统自带的实现类(五种):
/** * dbUtils通过QueryRunner的query(conn, sql, rst, args)来实现查询 * 提供了五种查询返回类型,通过传递不同的ResultSetHandler的实现类给rst实现 * 1.BeanHandler是输出查询到的第一个类型对象,是根据sql字段别名进行set设置的; * 2.BeanListHandler输出查询到的一组类型对象; * 3.MapHandler是输出一个map对象,键:字段名(不是别名),值是字段对应的值; * 4.MapListHandler是输出一个list对象,每个元素是个map对象,多组数据的键值; * 5.ScalarHandler输出的是查询到的第一行第一列的值,可以特定查询某个量 */ @Test public void testQueryRunnerQuery(){ Connection conn = null; try { conn = JDBCTools.getConnection(); String sql = "SELECT id, name customerName, email, birth FROM customers"; QueryRunner queryRunner = new QueryRunner(); //1.BeanHandler是输出查询到的第一个类型对象,是根据sql字段别名进行set设置的 // Customer customer = (Customer) queryRunner.query(conn, sql, new BeanHandler(Customer.class));对泛型不熟悉时的使用 Customer customer = queryRunner.query(conn, sql, new BeanHandler<>(Customer.class)) System.out.println(customer); //输出:Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12] //2.BeanListHandler输出查询到的一组类型对象 // List<Customer> customers = (List<Customer>) queryRunner.query(conn, sql, new BeanListHandler(Customer.class)); List<Customer> customers = queryRunner.query(conn, sql, new BeanListHandler<>(Customer.class)); System.out.println(customers); //输出:[Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12], Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01]] //3.MapHandler是输出一个map对象,键:字段名(不是别名),值是字段对应的值 Map<String, Object> map = queryRunner.query(conn, sql, new MapHandler()); System.out.println(map); //输出:{id=1, customerName=jack, email=jack@163.com, birth=1991-12-12} //4.MapListHandler是输出一个list对象,每个元素是个map对象,多组数据的键值 // List<Map<String, Object> > list = (List<Map<String, Object>>) queryRunner.query(conn, sql, new MapListHandler()); List<Map<String, Object> > list = queryRunner.query(conn, sql, new MapListHandler()); System.out.println(list); //输出结果:[{id=1, customerName=jack, email=jack@163.com, birth=1991-12-12}, {id=2, customerName=yuchen, email=yuchen@163.com, birth=1991-01-01}] //5.ScalarHandler输出的是查询到的第一行第一列的值,可以特定查询某个量 sql = "SELECT email From customers WHERE name = ?"; // String obj = (String) queryRunner.query(conn, sql, new ScalarHandler(),"yuchen"); String obj = queryRunner.query(conn, sql, new ScalarHandler<String>(),"yuchen"); System.out.println(obj); //输出:yuchen@163.com } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } }
15、利用DBUtils编写自己的DAO
编写自己的DAO,使用泛型,方便在程序开发中使用,主要采用DBUtils工具中的QueryRunner类,实现增删改查。定义接口DAO--->实现类DAOImpl--->适用于特定表(类)的CustomerDAO--->程序开发中使用CustomerDAO对象操作数据库
定义DAO接口
public interface DAO<T> { /** * 完成DELETE、INSERT、UPDATE操作 * @param conn 连接 * @param sql sql语句 * @param args 占位符参数 * @throws SQLException */ public void update(Connection conn, String sql, Object ... args ) throws SQLException; /** * 获取一行数据对应的一个对象 * @param conn * @param sql * @param args * @return * @throws SQLException */ public T get(Connection conn, String sql, Object ... args) throws SQLException; /** * 获取多行数据的List对象 * @param conn * @param sql * @param args * @return * @throws SQLException */ public List<T> getForList(Connection conn, String sql, Object ... args) throws SQLException; /** * 获取特定值(多行多列数据的第一行第一列数据) * @param conn * @param sql * @param args * @return * @throws SQLException */ public <E> E getForValue(Connection conn, String sql, Object ... args) throws SQLException; /** * 批处理数据,使适用于DELETE,INSERT、UPDATE * @param conn * @param sql * @param args * @throws SQLException */ public void batch(Connection conn, String sql, Object[] ... args) throws SQLException; }实现类DAOImpl:注意实现类中属性的定义,以及获取父类的泛型初始化Class<T> type
public class DAOImpl<T> implements DAO<T> { private QueryRunner queryRunner = null; private Class<T> type; public DAOImpl() { queryRunner = new QueryRunner(); //获取父类的泛型,赋值给type Type superClass = this.getClass().getGenericSuperclass(); ParameterizedType parameterizedType = (ParameterizedType)superClass; Type [] types = parameterizedType.getActualTypeArguments(); type = (Class<T>)types[0]; } @Override public void update(Connection conn, String sql, Object... args) throws SQLException { queryRunner.update(conn, sql, args); } @Override public T get(Connection conn, String sql, Object... args) throws SQLException { T t = queryRunner.query(conn, sql, new BeanHandler<T>(type), args); return t; } @Override public List<T> getForList(Connection conn, String sql, Object... args) throws SQLException { List<T> list = new ArrayList<T>(); list = queryRunner.query(conn, sql, new BeanListHandler<>(type), args); return list; } @Override public <E> E getForValue(Connection conn, String sql, Object... args) throws SQLException { E e = queryRunner.query(conn, sql, new ScalarHandler<E>(), args); return e; } @Override public void batch(Connection conn, String sql, Object[]... args) throws SQLException { queryRunner.batch(conn, sql, args); } }特定类的DAO:CustomerDAO
public class CustomerDAO extends DAOImpl <Customer> { }使用CustomerDAO
public class CustomerDAOTest { @Test public void testBatch(){ CustomerDAO customerDAO = new CustomerDAO(); Connection conn = null; try { conn = JDBCTools.getConnection(); String sql = "INSERT INTO customers VALUES(?,?,?,?)"; Object[] arg1 = new Object[]{4,"jack4","jack4@163.com","1990-1-1"} ; Object[] arg2 = new Object[]{5,"jack5","jack5@163.com","1990-1-1"} ; Object[] arg3 = new Object[]{6,"jack6","jack6@163.com","1990-1-1"} ; customerDAO.batch(conn, sql, arg1,arg2,arg3); } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } @Test public void testUpdate() { CustomerDAO customerDAO = new CustomerDAO(); Connection conn = null; try { conn = JDBCTools.getConnection(); String sql = "INSERT INTO customers VALUES(?,?,?,?)"; customerDAO.update(conn, sql, 3,"shenghua","shenghua@163.com","1990-11-11"); } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } @Test public void testGet() { CustomerDAO customerDAO = new CustomerDAO(); Connection conn = null; Customer customer = null; try { conn = JDBCTools.getConnection(); String sql = "SElECT id , name customerName, email, birth FROM customers WHERE name=?"; customer = customerDAO.get(conn, sql, "yuchen"); System.out.println(customer); //Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01] } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } @Test public void testGetForList() { CustomerDAO customerDAO = new CustomerDAO(); Connection conn = null; List<Customer> list = new ArrayList<Customer>(); try { conn = JDBCTools.getConnection(); String sql = "SElECT id , name customerName, email, birth FROM customers "; list = customerDAO.getForList(conn, sql); System.out.println(list); //[Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12], // Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01]] } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } @Test public void testGetValue() { CustomerDAO customerDAO = new CustomerDAO(); Connection conn = null; String obj = null; try { conn = JDBCTools.getConnection(); String sql = "SElECT email FROM customers WHERE name =? "; obj = customerDAO.getForValue(conn, sql, "yuchen"); System.out.println(obj);//yuchen@163.com } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } }
相关文章推荐
- MFC SDI 使窗口最大化,并不能改变窗口大小
- 让footer固定在页面(视口)底部(CSS-Sticky-Footer)
- WPF 使用Task代替ThreadPool和Thread
- windows 下vim使用LookupFile插件
- 复选框全选,添加
- 阿里云上我微服务架构
- zookeeper安装和应用场合(名字,配置,锁,队列,集群管理)
- ELK学习10_ELK系列--实时日志分析系统ELK 部署与运行中的问题汇总
- 最短路径—Dijkstra算法和Floyd算法
- Java基础系列二、代码结构+函数
- iOS开发日记53-CALayer和UIView
- 问题:css 自动换行;结果:CSS控制文本自动换行
- OneAPM Cloud Test——系统性能监控神器
- PAT-支票面额(基础编程题)
- Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)
- OneAPM Cloud Test——系统性能监控神器
- java设计模式----模板方法模式
- 删除已经配置的类库和移除CocoaPods
- Redis 视频教程 大数据 高性能 集群 NoSQL 设计 实战 入门 命令
- 关于VLAN-tag