一、数据库简单操作

1、JDBC连接数据库

数据库为了统一,出现了JDBC规范,每种数据库都提供连接时需要的jar包。我们可以通过JDBC-API来操作数据库。最简单的操作数据库如下:

public static void main(String[] args) throws SQLException {
//		1、注册驱动
		DriverManager.registerDriver(new com.mysql.jdbc.Driver());//ctrl+shit+t
//		2、获取与数据库的链接
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day15", "root", "sorry");
		System.out.println(conn.getClass().getName());
//		3、创建代表SQL语句的对象
		Statement stmt = conn.createStatement();
//		4、执行SQL语句
		ResultSet rs = stmt.executeQuery("select id,name,password,email,birthday from users");
//		5、如果是查询语句:返回结果集。
		while(rs.next()){
			System.out.println(rs.getObject("id"));
			System.out.println(rs.getObject("name"));
			System.out.println(rs.getObject("password"));
			System.out.println(rs.getObject("email"));
			System.out.println(rs.getObject("birthday"));
			System.out.println("---------------------");
		}
//		6、释放资源
		rs.close();
		stmt.close();
		conn.close();
	}

2、编写工具类JdbcUtil

数据库操作在很多业务都会用到,这么写代码很庞大,所以写出这个工具类,

与具体数据库解耦


配置文件

dbcfg.properties:


driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql:///day15
user=root
password=123456

工具类:


//与具体的数据库解耦
public class JdbcUtil {
	private static String driverClass;
	private static String url;
	private static String user;
	private static String password;
	
	static{
		try {
			InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("dbcfg.properties");
			Properties props = new Properties();
			props.load(in);
			
			driverClass = props.getProperty("driverClass");
			url = props.getProperty("url");
			user = props.getProperty("user");
			password = props.getProperty("password");
			Class.forName(driverClass);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
		
	}

	public static Connection getConnection() throws Exception {
		Connection conn = DriverManager.getConnection(url,user,password);
		return conn;
	}

	public static void release(ResultSet rs, Statement stmt, Connection conn) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if (stmt != null) {
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}


到此我们使用JDBC提供的API可以来操作数据库了,但是代码很繁琐,我们编写了工具类缩小代码量,但是多次数据库建立连接耗费资源大的问题没有解决,下面我们看如何实现数据库连接的优化。


二、优化数据库连接操作


建立连接是十分耗费资源的,提前建立连接,需要的时候取出连接。多个连接被线程池缓存。


javaee连接数据库失败 java ee连接数据库_Source

javaee连接数据库失败 java ee连接数据库_Source_02


1、模拟线程池


//模拟连接池
public class SimpleConnectionPool {
	//存放链接对象的池
	private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
	//最开始初始化一些链接到池中
	static{
		for(int i=0;i<10;i++){
			Connection conn = JdbcUtil.getConnection();
			pool.add(conn);
		}
	}
	//从池中获取一个链接
	public static Connection getConnection(){
		if(pool.size()>0){
			return pool.remove(0);
		}else{
			throw new RuntimeException("服务器忙");
		}
	}
	//用完后还回池中
	public static void release(Connection conn){
		pool.add(conn);
	}
}

测试自己写的数据库连接池:

public class SimpleConnectionPoolUseDemo {
	public static void main(String[] args) {
		//保存
		Connection conn = SimpleConnectionPool.getConnection();
		//........
		SimpleConnectionPool.release(conn);
		
		//删除
		Connection conn1 = SimpleConnectionPool.getConnection();
		//........
		SimpleConnectionPool.release(conn1);
		
	}
}


各大数据库厂商进行了自己数据库驱动的规范,出现了JDBC规范。JDBC中提供了一个DataSource接口,那么实现标准的数据源就要实现这个接口。


我们自己模拟了线程池来提高效率,那么已经有现成的框架来实现这个功能了。如下:

2、DataSource之DBCP框架



(1)是apache的开源组件。导入jar包。

commons-dbcp.jar

commons-pool.jar

mysql-connector-java-5.0.8-bin.jar

(2)添加配置文件,参考资料中的dbcpconfig.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=sorry
initialSize=10

maxActive=50

maxIdle=20

minIdle=5

maxWait=60000

connectionProperties=useUnicode=true;characterEncoding=utf8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_COMMITTED



(3)编写工具类


public class DBCPUtil {
	private static DataSource dataSource;
	static{
		try {
			//读取配置文件,初始化数据源
			InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
			Properties props = new Properties();
			props.load(in);
			dataSource = BasicDataSourceFactory.createDataSource(props);
		}  catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
		
	}
	
	public static DataSource getDataSource(){
		return dataSource;
	}
	public static Connection getConnection(){
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			throw new RuntimeException("获取数据库连接失败");
		}
	}
}

(4)测试:

public class DBCPUtilTest {

	public static void main(String[] args) throws SQLException {
		Connection conn = DBCPUtil.getConnection();
		System.out.println(conn.getClass().getName());
		conn.close();//还回池中
	}
}



javaee连接数据库失败 java ee连接数据库_Source_03




3、DataSource之C3P0框架



(1)开源的数据源。c3p0-0.9.1.2.jar

(2)编写配置文件,自动注入

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<default-config>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql:///test</property>
		<property name="user">root</property>
		<property name="password">sorry</property>
		<property name="initialPoolSize">10</property>
		<property name="maxIdleTime">30</property>
		<property name="maxPoolSize">30</property>
		<property name="minPoolSize">10</property>
		<property name="maxStatements">200</property>
	</default-config> <!-- This app is massive! -->
	<named-config name="day16">
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql:///day16</property>
		<property name="user">root</property>
		<property name="password">sorry</property>
		<property name="initialPoolSize">10</property>
		<property name="maxIdleTime">30</property>
		<property name="maxPoolSize">100</property>
		<property name="minPoolSize">10</property>
		<property name="maxStatements">200</property>
	</named-config>
</c3p0-config>

(3)工具类

public class C3P0Util {
	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();//默认读default-config
	
	public static DataSource getDataSource(){
		return dataSource;
	}
	public static Connection getConnection(){
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			throw new RuntimeException("获取数据库连接失败");
		}
	}
	
}

(4)测试

public class C3P0UtilTest {
	public static void main(String[] args) throws SQLException {
		Connection conn = C3P0Util.getConnection();
		System.out.println(conn.getClass().getName());
		conn.close();//还回池中
	}
}



通过以上我们(1)开始创建了JdbcUtil 来缩小代码(2)引入了数据源的概念,使用DBCP和C3P0来提高数据库访问的效率。此时也不需要JdbcUtil了,而是采用框架提供的默认配置文件接口来实现数据库信息的自动注入。优化了数据库连接效率,接下来就是优化一些数据操作语句的优化。让数据库操作更简便。


三、自定义JDBC框架

目的:简化数据库操作语音,是数据库的CRUD更简单明了。当然后面有很多框架做了这个事,就是Hibernate和mybatis。

我们在之前的优化上进行框架的封装。

封装步骤

(1)拷贝数据库jar和数据源框架DBCP的jar

(2)DBCPUtil和dbcpconfig.properties

(3)DBAssist类,传入数据源信息。

a、此类需要传入数据源信息

b、实现update(sql,占位符)函数,来进行增删改操作

c、实现query(sql,封装结构,占位符)函数,来执行查询操作

d、实现release函数,来释放资源


public class DBAssist {
	
	private DataSource dataSource;
	public DBAssist(DataSource dataSource){
		this.dataSource = dataSource;
	}
	
	//sql语句中的参数占位符要和params对应。
	/**
	 * 执行DML语句:INSERT UPDATE DELETE
	 * @param sql
	 * @param params
	 */
	public void update(String sql,Object...params) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try{
			conn = dataSource.getConnection();
			stmt = conn.prepareStatement(sql);
			//设置参数
			ParameterMetaData pmd = stmt.getParameterMetaData();
			int num = pmd.getParameterCount();//得到sql语句中的参数个数
			
			if(num>0){
				if(params==null){
					throw new IllegalArgumentException("有占位符,但没有传入具体的参数值");
				}
				if(num!=params.length){
					throw new IllegalArgumentException("参数个数不匹配");
				}
				for(int i=0;i<num;i++){
					stmt.setObject(i+1, params[i]);
				}
			}
			
			stmt.executeUpdate();
		}catch(Exception e){
			throw new RuntimeException(e);
		}finally{
			release(rs, stmt, conn);
		}
		
	}
	//执行查询:把结果封装到对象中
	public Object query(String sql,ResultSetHandler handler,Object...params) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try{
			conn = dataSource.getConnection();
			stmt = conn.prepareStatement(sql);
			//设置参数
			ParameterMetaData pmd = stmt.getParameterMetaData();
			int num = pmd.getParameterCount();//得到sql语句中的参数个数
			
			if(num>0){
				if(params==null){
					throw new IllegalArgumentException("有占位符,但没有传入具体的参数值");
				}
				if(num!=params.length){
					throw new IllegalArgumentException("参数个数不匹配");
				}
				for(int i=0;i<num;i++){
					stmt.setObject(i+1, params[i]);
				}
			}
			
			rs = stmt.executeQuery();//得到结果集对象。具体怎么封装:谁用谁知道
			
			return handler.handle(rs);
			
		}catch(Exception e){
			throw new RuntimeException(e);
		}finally{
			release(rs, stmt, conn);
		}
		
	}
	private void release(ResultSet rs, Statement stmt, Connection conn) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if (stmt != null) {
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

(4)查询结果集的封装

增删改操作没有结果集,所以不涉及结果集的封装,查询语句会返回结果集,如:select * from table;select * from table where name=;

我们实现一个接口:ResultSetHandler,查询的时候传入接口的具体实现来封装结果,怎么封装在接口实现里自己写。

import java.sql.ResultSet;

public interface ResultSetHandler {
	/**
	 * 把结果集中的数据封装到对象中
	 * @param rs
	 * @return
	 */
	Object handle(ResultSet rs);
}

(5)

ResultSetHandler接口实现之返回结果只有一个对象 BeanHandler

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
//类的字段名和数据库的字段名保持一致
//只适合结果集只有一条的情况。返回的是一个JavaBean
public class BeanHandler implements ResultSetHandler {
	private Class clazz;//目标类型
	public BeanHandler(Class clazz){
		this.clazz = clazz;
	}
	public Object handle(ResultSet rs) {
		try {
			if(rs.next()){
				Object bean = clazz.newInstance();
				
				ResultSetMetaData rsmd = rs.getMetaData();
				int num = rsmd.getColumnCount();//列数
				for(int i=0;i<num;i++){
					String fieldName = rsmd.getColumnName(i+1);
					Object fieldValue = rs.getObject(i+1);
					//反射字段
					Field f = clazz.getDeclaredField(fieldName);//私有的
					f.setAccessible(true);
					f.set(bean, fieldValue);
				}
				
				return bean;
			}else{
				return null;
			}
		}catch(Exception e) {
			throw new RuntimeException("封装数据失败");
		}
		
		
	}

}



(6)

ResultSetHandler接口实现之返回list  BeanListHandler

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class BeanListHandler implements ResultSetHandler {
	private Class clazz;//目标类型
	public BeanListHandler(Class clazz){
		this.clazz = clazz;
	}
	public Object handle(ResultSet rs) {
		try {
			List list = new ArrayList();
			while(rs.next()){
				Object bean = clazz.newInstance();
				
				ResultSetMetaData rsmd = rs.getMetaData();
				int num = rsmd.getColumnCount();//列数
				for(int i=0;i<num;i++){
					String fieldName = rsmd.getColumnName(i+1);
					Object fieldValue = rs.getObject(i+1);
					//反射字段
					Field f = clazz.getDeclaredField(fieldName);//私有的
					f.setAccessible(true);
					f.set(bean, fieldValue);
				}
				list.add(bean);	
			}
			
			return list;
		}catch(Exception e) {
			throw new RuntimeException("封装数据失败");
		}
	}

}



(7)测试

import java.util.List;
import org.junit.Test;
import com.itheima.dbassist.BeanHandler;
import com.itheima.dbassist.BeanListHandler;
import com.itheima.dbassist.DBAssist;
import com.itheima.domain.Account;
import com.itheima.util.DBCPUtil;

public class DBAssistTest {
	private DBAssist da = new DBAssist(DBCPUtil.getDataSource());
	@Test
	public void testAdd(){
		da.update("insert into account (id,name,money) values(?,?,?)", 6,"fff",10000);
	}
	@Test
	public void testUpdate(){
		da.update("update account set money=? where id=?",1000,6);
	}
	@Test
	public void testDelete(){
		da.update("delete from account where id=?",6);
	}
	
	@Test
	public void testFindOne(){
		Account a = (Account)da.query("select * from account where id=6", new BeanHandler(Account.class));
		System.out.println(a);
	}
	@Test
	public void testFind(){
		List list = (List)da.query("select * from account", new BeanListHandler(Account.class));
		for(Object o:list)
			System.out.println(o);
	}
}



四、DBUtils框架


  apache推出的对JDBC编码进行了简单封装的一个框架。

 核心类:QueryRunner


public class DBUtilDemo {
	private QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
	@Test
	public void test1() throws SQLException{
		qr.update("insert into person values(?,?,?)", 1,"zxn","1998-09-08");
	}
	@Test
	public void test11() throws SQLException{
		qr.update("insert into person values(?,?,?)", 2,"zxnn",new Date());
	}
	@Test
	public void test2() throws SQLException{
		qr.update("update person set name=? where id=?", "lxj",2);
	}
	@Test
	public void test3() throws SQLException{
		qr.update("delete from person where id=?", 2);
	}
	//批处理
	@Test
	public void test4() throws SQLException{
		//高维:插入的记录条数    低维:每条记录的参数
		Object [][]params = new Object[10][];
		
		for(int i=0;i<10;i++){
			params[i] = new Object[]{i+1,"aaa"+(i+1),new Date()};
		}
		
		qr.batch("insert into person values(?,?,?)", params);
	}

}



结果处理器

javaee连接数据库失败 java ee连接数据库_bc_04

BeanHandler和BeanListHandler和之前我们自定义的一样。

public class ResultSetHandlerDemo {
	private QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
	//ArrayHandler:适合结果只有一条的情况。把记录中的每个字段的值封装到了一个Object[]中。
	@Test
	public void test1() throws SQLException{
		Object[] objs = qr.query("select * from person", new ArrayHandler());
		for(Object obj:objs)//每个元素就是列的值
			System.out.println(obj);
	}
	//ArrayListHandler:多条结果。把每条记录的列值封装到Object[],再把Object[]封装到List中
	@Test
	public void test2() throws SQLException{
		List<Object[]> list = qr.query("select * from person", new ArrayListHandler());
		for(Object[] objs:list){
			System.out.println("----------------------");
			for(Object obj:objs)//每个元素就是列的值
				System.out.println(obj);
		}
	}
	//ColumnListHandler:封装某列的值。把取出的列值封装到List中。
	@Test
	public void test3() throws SQLException{
		List<Object> list = qr.query("select * from person", new ColumnListHandler("name"));
		for(Object obj:list){
			System.out.println(obj);
		}
	}
	//KeyedHandler:适合有多条记录的情况
	@Test
	public void test4() throws SQLException{
		Map<Object, Map<String,Object>> bmap = qr.query("select * from person", new KeyedHandler("id"));
		for(Map.Entry<Object, Map<String,Object>> bme:bmap.entrySet()){
			System.out.println("------------------");
			for(Map.Entry<String, Object> lme:bme.getValue().entrySet()){
				System.out.println(lme.getKey()+":"+lme.getValue());
			}
		}
	}
	//MapHandler:适合结果有一条的情况。把记录的列明作为key,列值作为value封装到一个Map中
	@Test
	public void test5() throws SQLException{
		Map<String,Object> map= qr.query("select * from person", new MapHandler());
		for(Map.Entry<String, Object> me:map.entrySet()){
			System.out.println(me.getKey()+":"+me.getValue());
		}
	}
	//MapListHandler:适合多条记录的结果。把记录的列明作为key,列值作为value封装到一个Map中。再把Map放到List中
	@Test
	public void test6() throws SQLException{
		List<Map<String,Object>> list= qr.query("select * from person", new MapListHandler());
		for(Map<String,Object> map:list){
			System.out.println("------------------");
			for(Map.Entry<String, Object> me:map.entrySet()){
				System.out.println(me.getKey()+":"+me.getValue());
			}
		}
	}
	//ScalarHandler:适合结果只有一条且只有一列情况。
	@Test
	public void test7() throws SQLException{
		Object obj = qr.query("select count(*) from person", new ScalarHandler(1));
		System.out.println(obj.getClass().getName());
		System.out.println(obj);
	}
}





到此在不使用Hibernate和mybatis框架的情况下,我们通过JDBC框架,可以很方便的来操作数据库了。实现了查询语句到javabean的封装,后面我们学习Hibernate和mybatis后将更方便的实现数据的CRUD和表与实体之间关系的建立。