[置顶] JDBC学习笔记

JDBC是什么?为什么要使用JDBC呢?

大家都知道,有各种数据库可以使用,如mysql、oracle、DB2等,那么在程序开发过程中,如何保证程序与数据库的独立性,以及降低访问数据库的操作繁杂性,引入了JDBC(Java Data Base Connectivity),相当于一种介质,负责java程序与数据库的沟通。JDBC实际上提供了一套标准,一套接口,各数据厂商安装这个标准去开发自己的数据库软件,从而隐藏了各数据库软件底层的差异,使用者只需调用这些标准的接口,就能完成一系列对数据库的操作。

1、Driver接口与DriverManager

Driver接口

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

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','[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中。

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);
	}
	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查询语言非常熟悉,才能使用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 protected], 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, "[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);
		}
	}

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,"[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”图片在项目的根目录下。
从数据库读出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","[email protected]","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 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);
		}
		
	}

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","[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);
		}
	}

}












你可能感兴趣的:(jdbc,sql注入,DbUtils,DAO设计)