JDBC是什么?为什么要使用JDBC呢?
大家都知道,有各种数据库可以使用,如mysql、oracle、DB2等,那么在程序开发过程中,如何保证程序与数据库的独立性,以及降低访问数据库的操作繁杂性,引入了JDBC(Java Data Base Connectivity),相当于一种介质,负责java程序与数据库的沟通。JDBC实际上提供了一套标准,一套接口,各数据厂商安装这个标准去开发自己的数据库软件,从而隐藏了各数据库软件底层的差异,使用者只需调用这些标准的接口,就能完成一系列对数据库的操作。
Driver接口是JDBC这套标准接口中,最基本的接口,每个数据库厂商都必须实现这个接口以及其提供的方法。例如mysql数据库的该接口的实现类就是com.mysql.jdbc.Driver
Driver接口中有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可以注册多个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
利用DriverManager获取连接之后,就需要Statement来执行我们需要的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','[email protected]','1990-12-12' )"; // sql= "DELETE FROM customers WHERE id=2;"; // sql="UPDATE customers SET NAME='yy', EMAIL= '[email protected]', 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中。
结果集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); } } }
我们经常说以面向对象的方法写程序,那么在向数据库中输入信息时,该如何利用面向对象的思想呢?
其实,一个数据库的表,就可以看作为一个类型,表中的每一行数据都可以作为一个对象看待,所以,向一个表中输入数据,就先构造该表的一个类,用该类的对象,进行信息的输入。
数据库中的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这个类。
的使用好处,其一就是避免了拼写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); } 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); } }为了抽离出PreparedStatement的使用,对于所有数据库的操作都适合,建立一个工具类方法update
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); } 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()); }这样基于PreparedStatement的update方法就可以被其他程序使用了。
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); } }输出结果 用户或密码错误!
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; }输出结果;
public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; }上述代码有set 和 get 方法,故,该类型对象有idCard属性
@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); }
public String getIdCard2() { return idCard; } public void setIdCard3(String idCard) { this.idCard = idCard; }SetProperty 和 GetProperty就需要改为:
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; }
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 } }
@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); } }输出结果:
@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, "[email protected]"); 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); } }
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,"[email protected]"); 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”图片在项目的根目录下。
@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); } }
@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); } }
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
@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); } }
Connection connection = DriverManager.getConnection(url, user, password);为什么使用数据库连接池呢?主要有以下几点
@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
@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>
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(); } } }
/** * 使用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","[email protected]","1991-12-12"); } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } }使用query方法,实际上有多个重载的方法,常用的是query(conn, sql, rst, args)
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 protected], 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 protected], 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 protected], birth=1991-12-12], Customer [id=2, name=yuchen, [email protected], 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 protected], 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 protected], birth=1991-12-12}, {id=2, customerName=yuchen, [email protected], 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); //输出:[email protected] } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } }
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","[email protected]","1990-1-1"} ; Object[] arg2 = new Object[]{5,"jack5","[email protected]","1990-1-1"} ; Object[] arg3 = new Object[]{6,"jack6","[email protected]","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","[email protected]","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 protected], 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 protected], birth=1991-12-12], // Customer [id=2, name=yuchen, [email protected], 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);//[email protected] } catch (SQLException e) { e.printStackTrace(); } finally{ JDBCTools.release(null, null, conn); } } }