一、数据库连接池原理:(理解)
//模拟数据库连接池的原理
public class ConnectionPoolDemo {
private static List<Connection> pool = new ArrayList<Connection>();
static{
try {
for(int i=0;i<10;i++){
Connection conn = JdbcUtil.getConnection();//创建的新连接
pool.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//从池中取出一个链接
public synchronized static Connection getConnection(){
if(pool.size()>0){
Connection conn = pool.remove(0);
return conn;
}else{
throw new RuntimeException("服务器真忙");
}
}
//把链接还回池中
public static void release(Connection conn){
pool.add(conn);
}
}
二、编写数据源(DataSource)(很重要)
编写一个类实现javax.sql.DataSource
public class MyDataSource1 implements DataSource {
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
static{
try {
for(int i=0;i<10;i++){
Connection conn = JdbcUtil.getConnection();//创建的新连接
pool.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//从池中获取链接 > com.mysql.jdbc.Connection
public Connection getConnection() throws SQLException {
if(pool.size()>0){
Connection conn = pool.remove(0);
MyConnection1 mconn = new MyConnection1(conn,pool);
return mconn;
}else{
throw new RuntimeException("服务器真忙");
}
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public Connection getConnection(String username, String password)
throws SQLException {
return null;
}
}
三、编程的难点:(设计模式)
难点:用一个实现了javax.sql.DataSource类的实例时,用户如果调用Connection.close()方法,会把链接关闭,失去了连接池的意义。
明确一个问题:用户得到Connection的实现是:数据库驱动对Connection接口的实现。因此,调用的close方法都是数据库驱动的,它会把链接给关闭。(这不是我们要的,我们要把该链接换回池中)。
解决方案:改写驱动原有的close方法。对已知类的某个/某些方法进行功能上的改变,有以下几种编码方案:
a、继承:此处行不通。
到底针对哪个驱动的实现写子类(很多)
数据库驱动对Connection接口的实现类,不允许被继承
丢失了原有对象的信息。捡了芝麻丢了西瓜。
b、装饰(包装)设计模式:(基础IO)
保持被包装对象的原有信息,又可以对某个/某些方法进行改写。
口诀:
1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为)
2、定义一个变量,引用被包装类的实例。
3、定义构造方法,传入被包装类的实例。
4、对于要改写的方法,编写自己的代码即可。
5、对于不需要改写的方法,调用原有对象的对应方法。
//目前要包装的是:com.mysql.jdbc.Connection
//1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为)
public class MyConnection implements Connection {
// 2、定义一个变量,引用被包装类的实例
private Connection conn;//引用具体的数据库驱动
private List<Connection> pool;
// 3、定义构造方法,传入被包装类的实例。
public MyConnection(Connection conn,List<Connection> pool){//依赖注入
this.conn = conn;
this.pool = pool;
}
//把链接还回池中
// 4、对于要改写的方法,编写自己的代码即可。
public void close() throws SQLException {
pool.add(conn);
}
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
//5、对于不需要改写的方法,调用原有对象的对应方法。
public <T> T unwrap(Class<T> iface) throws SQLException {
return conn.unwrap(iface);
}
c、默认适配器:(为了后来做准备)
//默认的适配器
/*
本身也是一个包装类,但并没有对任何的方法进行改写
1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为)
2、定义一个变量,引用被包装类的实例。
3、定义构造方法,传入被包装类的实例。
4、全部调用原有对象的对应方法
*/
public class ConnectionAdapter implements Connection {
private Connection conn;
public ConnectionAdapter(Connection conn){
this.conn = conn;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return conn.unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return conn.isWrapperFor(iface);
}
/*
这也是包装:对ConnectionAdapter进行包装。
包装类即是被包装类的包装,又是他的子类。
1、编写一个类,继承已经是包装类的类。
2、定义一个变量,引用被包装类的实例。
3、定义构造方法,传入被包装类的实例。
4、覆盖掉需要改写的方法
*/
public class MyConnection1 extends ConnectionAdapter {
private Connection conn;
private List<Connection> pool;
public MyConnection1(Connection conn,List<Connection> pool){
super(conn);
this.conn = conn;
this.pool = pool;
}
public void close() throws SQLException {
pool.add(conn);
}
}
d、动态代理:(很重要 AOP--Aspect-Oriented Programming 核心技术)
l 基于接口的动态代理:Proxy
如果一个类没有实现任何的接口,此种代理就不能使用了。
package com.itheima.proxy;
public interface Human {
void sing(float money);
void dance(float money);
}
package com.itheima.proxy;
public class SpringBrother implements Human {
public void sing(float money) {
System.out.println("拿到钱:"+money+"开唱");
}
public void dance(float money) {
System.out.println("拿到钱:"+money+"开跳");
}
}
package com.itheima.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client1 {
public static void main(String[] args) {
final Human sb = new SpringBrother();
//代理人:如何动态产生代理人
/*
ClassLoader loader:动态代理,必须有字节码class。加到内存中运行,必须有类加载器。固定:和被代理人用的是一样的
Class<?>[] interfaces:代理类要实现的接口,要和被代理对象有着相同的行为。固定:和被代理人用的是一样的
InvocationHandler h:如何代理。他是一个接口。策略设计模式。
*/
//产生代理类,得到他的实例
Human proxyMan = (Human)Proxy.newProxyInstance(sb.getClass().getClassLoader(),
sb.getClass().getInterfaces(),
new InvocationHandler() {
//匿名内部类,完成具体的代理策略
//调用代理类的任何方法,都会经过该方法。 拦截
/*
Object proxy:对代理对象的引用。
Method method:当前执行的方法
Object[] args:当前方法用到的参数
返回值:当前调用的方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//判断出场费
if("sing".equals(method.getName())){
//唱歌
float money = (Float)args[0];
if(money>10000){
method.invoke(sb, money/2);
}
}
if("dance".equals(method.getName())){
//唱歌
float money = (Float)args[0];
if(money>20000){
method.invoke(sb, money/2);
}
}
return null;
}
}
);
proxyMan.sing(20000);
proxyMan.dance(100000);
}
}
l 基于子类的动态代理:CGLIB
前提:被代理类的要求
1、不能是final的
2、必须是public的
package com.itheima.cglib;
public class SpringBrother{
public void sing(float money) {
System.out.println("拿到钱:"+money+"开唱");
}
public void dance(float money) {
System.out.println("拿到钱:"+money+"开跳");
}
}
package com.itheima.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class Client1 {
public static void main(String[] args) {
final SpringBrother sb = new SpringBrother();
//产生sb的代理:
/*
Class type:代理类的父类型
Callback cb:回调,如何代理
*/
SpringBrother proxy = (SpringBrother) Enhancer.create(SpringBrother.class,new MethodInterceptor(){
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy arg3) throws Throwable {
//判断出场费
if("sing".equals(method.getName())){
//唱歌
float money = (Float)args[0];
if(money>10000){
method.invoke(sb, money/2);
}
}
if("dance".equals(method.getName())){
//唱歌
float money = (Float)args[0];
if(money>20000){
method.invoke(sb, money/2);
}
}
return null;
}
});
System.out.println(proxy instanceof SpringBrother);
proxy.dance(100000);
proxy.sing(50000);
}
}
比如普通的JavaBean就可能没有实现任何的接口。代理类是被代理类的子类。
四、开源数据源的使用:(很重要,非常简单)
1、DBCP:
Apache组织开发的。DBCP:DataBase Connection Pool,对数据源的一种实现。
a、拷贝jar包
b、编写配置文件
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
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
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=REPEATABLE_READ
c、使用即可
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(e);
}
}
}
2、C3P0:
开源数据源的实现。
c3p0-config.xml
<?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">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<named-config name="day15">
<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>
public class C3P0Util {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource(){
return dataSource;
}
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
3、更接近实际开发:JNDI管理数据源
JNDI:Java Naming and Directory Interface。属于JavaEE技术之一,目的模仿window系统中的注册表。
a、在服务器中注册JNDI数据源
1、拷贝数据库的驱动到Tomcat\lib目录下
2、在web应用的META-INF目录下建立一个名称为context.xml的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
maxActive="20" maxIdle="5" maxWait="10000"
username="root" password="sorry" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/test"/>
</Context>
3、获取JNDI容器中的资源
public class JndiDsUtil {
public static Connection getConnection() throws Exception {
Context initContext = new InitialContext();
DataSource ds = (DataSource) initContext
.lookup("java:/comp/env/jdbc/test");
Connection conn = ds.getConnection();
return conn;
}
}
五、编写自己的JDBC框架(为学习DBUtil框架、Spring JDBCTemplate做准备)
1、数据库元信息的获取(为写框架而准备)
元信息:数据库的一些定义信息。比如用的是什么数据库等,表的定义信息等。
DatabaseMetaData PreparedStatement ResultSetMetaData getColumnCount
//数据库元信息的获取
public class Demo {
//数据库本身信息的获取
@Test
public void test1() throws Exception{
Connection conn = DBCPUtil.getConnection();
DatabaseMetaData dmd = conn.getMetaData();
String name = dmd.getDatabaseProductName();//能知道说什么方言
System.out.println(name);
int isolation = dmd.getDefaultTransactionIsolation();
System.out.println(isolation);
}
//参数元数据信息:PreparedStatement时
@Test
public void test2() throws Exception{
Connection conn = DBCPUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement("??????????");
ParameterMetaData pmd = stmt.getParameterMetaData();
int count = pmd.getParameterCount();
System.out.println(count);//统计语句中的占位符个数
}
//结果集元数据信息:
@Test
public void test3()throws Exception{
Connection conn = DBCPUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement("select * from account");
ResultSet rs = stmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int count = rsmd.getColumnCount();//有几列
System.out.println(count);
for(int i=0;i<count;i++){
String fieldName = rsmd.getColumnName(i+1);
int type = rsmd.getColumnType(i+1);
System.out.println(fieldName+":"+type);
}
}
}
2、编写JDBC框架:(策略设计模式)
/**
* 框架的核心类
* @author wzhting
*
*/
public class DBAssist {
private DataSource dataSource;
public DBAssist(DataSource dataSource){
this.dataSource = dataSource;
}
//写:添加、删除、修改
//params参数要和sql中的占位符对应
public void update(String sql,Object...params) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
stmt = conn.prepareStatement(sql);
//设置参数
//得到sql中的参数
ParameterMetaData pmd = stmt.getParameterMetaData();
int count = pmd.getParameterCount();
if(count>0){
if(params==null){
throw new RuntimeException("必须传入参数的值");
}
if(count!=params.length){
throw new RuntimeException("参数数量不匹配");
}
for(int i=0;i<count;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 rsh,Object...params) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
stmt = conn.prepareStatement(sql);
//设置参数
//得到sql中的参数
ParameterMetaData pmd = stmt.getParameterMetaData();
int count = pmd.getParameterCount();
if(count>0){
if(params==null){
throw new RuntimeException("必须传入参数的值");
}
if(count!=params.length){
throw new RuntimeException("参数数量不匹配");
}
for(int i=0;i<count;i++){
stmt.setObject(i+1, params[i]);
}
}
rs = stmt.executeQuery();
//有结果集,要封装到对象中。策略设计模式
return rsh.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();
}
conn = null;
}
}
}
public interface ResultSetHandler {
/**
* 把结果中的数据封装到指定的对象中
* @param rs
* @return 封装了数据的对象
*/
Object handle(ResultSet rs);
}
/**
* 适合只有一条查询结果的情况
* 封装到JavaBean中
* 满足约定:数据库字段名和JavaBean字段名保持一致
* @author wzhting
*
*/
public class BeanHanlder implements ResultSetHandler {
private Class clazz;//目标类型
public BeanHanlder(Class clazz){
this.clazz = clazz;
}
public Object handle(ResultSet rs) {
try {
if(rs.next()){
//有记录
Object bean = clazz.newInstance();//目标对象
//有多少列,列名和值又是什么?
ResultSetMetaData rsmd = rs.getMetaData();
int count = rsmd.getColumnCount();//列数
for(int i=0;i<count;i++){
String fieldName = rsmd.getColumnName(i+1);//得到数据库字段名,也就得到了JavaBan的字段名
Object fieldValue = rs.getObject(fieldName);//字段值
//通过字段反射
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(bean, fieldValue);
}
return bean;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 封装到JavaBean中
* 满足约定:数据库字段名和JavaBean字段名保持一致
* @author wzhting
*
*/
public class BeanListHanlder implements ResultSetHandler {
private Class clazz;//目标类型
public BeanListHanlder(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 count = rsmd.getColumnCount();//列数
for(int i=0;i<count;i++){
String fieldName = rsmd.getColumnName(i+1);//得到数据库字段名,也就得到了JavaBan的字段名
Object fieldValue = rs.getObject(fieldName);//字段值
//通过字段反射
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(bean, fieldValue);
}
list.add(bean);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}