1、数据库连接池
(1)现象:
多线程数据库访问中 ,随着线程结束数据库连接销毁,线程被释放。
每个线程都需要建立数据库连接,耗时,用户响应时间很慢。
(2)数据库连接池
1)连接复用
多线程共用后端物理连接,实现连接复用。
连接池管理物理连接,每个线程租借连接,用完后归还。
2)限流连接
为限制并发访问连接数,数据库服务器端设置最大并发连接数,若超过最大连接数抛 too many connections exception。
一般,在客户端Java程序中实现业务线程排队获取数据库连接限制同时获取数据库连接数,限流来保护后端数据库。
(3)概念
java jar包,介于java应用程序和jdbc物理连接之间,负责帮Java程序管理jdbc连接。
通过连接池暴露的接口,java程序可以获取数据库连接,使用完毕后归还给连接池。
连接池对JDBC连接有效管理,当连接数不足时自动创建连接,空闲连接较多时自动销毁连接,
多线程同时获取连接时提供排队等待保证应用程序有序获得连接。
(4)连接池组件
commons-dbcp.jar、commons-pool.jar、commons-logging.jar
package com.jdbc.pool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.apache.commons.dbcp2.BasicDataSource;
public class DBPoolTest {
private static BasicDataSource ds = null;
private static String DB_DRIVER="com.mysql.jdbc.Driver";
private static String DB_URL="jdbc:mysql://192.168.198.128:3306/cloud_study";
private static String USER="root";
private static String PASS="123456";
public static void initDbPool() {
//1、创建连接池对象
ds=new BasicDataSource();
ds.setDriverClassName(DB_DRIVER);
ds.setUrl(DB_URL);
ds.setUsername(USER);
ds.setPassword(PASS);
}
public void DbPoolTest() {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//2、获取连接
conn=ds.getConnection();
String sql="select * from user";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//3、释放连接, .close(): dbcp中归还连接给连接池
if(conn!=null)
conn.close();
if(ps!=null)
ps.close();
if(rs!=null)
rs.close();
} catch (Exception e2) {
}
}
}
public static void main(String[] args) {
initDbPool();
new DBPoolTest().DbPoolTest();
}
}
(4)连接池参数设置
实现类 BasicDataSource:.setInitialSize()、.setMaxTotal()、.setMaxWaitMillis()、.setMaxIdle()
1)setInitialSize() - 提高首次访问速度
应用程序首次访问数据库加载慢,设置InitialSize 提高首次访问速度
应用启动时向连接池预置一定数量连接保证应用程序首次访问数据库库时,连接池中就有一定数量连接。
InitialSize值=预期业务平均访问量
2)setMaxTotal() - 最大连接数
并发访问数>线程池连接数时,线程池新建连接,直到MaxTotal.
当连接数=MaxTotal时,不会创建新连接,强制线程进入等待队列。
3)setMaxWaitMillis() - 队列最大等待时间
4)setMaxIdle() - 最大空闲连接数
空闲连接数 > MaxIdle时,自动销毁该连接。
空闲连接数 < MinIdle时,自动创建新连接。
一般 MaxIdle=MinIdle,避免频繁创建/销毁连接。
5)DBCP定期检查
实现类 BasicDataSource-.setTestWhileIdle(True)、.setMinEvictableIdleTimeMills()、.setTimeBetweenEvictionRunsMillis()
数据库服务端为释放空闲等待的资源,默认自动关闭空闲时间超过阈值的连接,如mysql 阈值=8 hour.
服务器关闭连接后,客户端连接池不知道连接已关闭,应用程序使用该失效连接时会抛异常,应尽量保持连接池中连接都有效。
定期检查连接池中连接空闲时间,保证服务器关闭连接前销毁该连接。
一般,MinEvictableIdleTimeMillis<服务器阈值。
注意:(1) mysql show processlist; //mysql显示进程列表
(2)Java三种连接池(druid、c3p0、dbcp)
2、Java数据库连接-JDBC
(1)JDBC概念
Java提供的访问数据库的接口规范,接口具体实现由不同厂商的数据库驱动完成。
(2)JDBC常用API
Driver:接口,驱动程序的抽象
通过操作Driver,即可实现对各个驱动程序的操作。
DriverManager:Driver的管理类。
通过Class.forname(DriverName)向DriverManger注册一个驱动程序。
Connection 对象:数据库的物理连接
通过DriverManger.getConnection()方法建立该驱动程序到后端数据库的物理连接。
statement 对象: 实际sql容器/操作sql对象
通过conn.createStatement()/conn.prepareStatement(sql)可以创建一个或多个statement对象。
resultSet:结果集,行和列组成的二元表
* .beforeFirst()方法,调用后再调用.next()光标将指向第一行记录。
JDBC URL:后端数据库的唯一标识符
常用的URL:
mysql - jdbc:mysql://<ip>:<port>/database
oracle - jdbc:oracle:thin:@<ip>:<port>/database
SQL server - jdbc:microsoft:sqlserver://<ip>:<port>;DatabaseName=database
(3)JDBC - Java程序
步骤:装载驱动程序 -> 建立数据库连 -> 接执行sql语句 -> 获取执行结果 -> 清理环境
public class TestDemo {
private static String JDBC_DRIVER="com.mysql.jdbc.Driver";
private static String DB_URL="jdbc:mysql://192.168.198.128:3306/cloud_study";
private static String USER="root";
private static String PASS="123456";
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//1.加载数据库驱动
Class.forName(JDBC_DRIVER);
//2.获取数据库连接
conn = DriverManager.getConnection(DB_URL, USER, PASS);
String sql="select * from user";
//3.创建statment
stmt = conn.createStatement();
//4.执行SQL语句
rs = stmt.executeQuery("select userName from user");
while(rs.next()) {
System.out.println("hello "+rs.getString("userName"));
}
} catch (Exception e) {
//5.异常处理
e.printStackTrace();
}finally {
//6.清理环境
try {
if(conn!=null)
conn.close();
if(stmt!=null)
stmt.close();
if(rs!=null)
rs.close();
} catch (Exception e) {
}
}
}
}
(4)JDBC 高级使用功能
(a)提高编译时间,防止SQL注入
preparement:预编译,提交多个sql,同时防止sql注入
(b) 读取多条记录,内存溢出
解决:游标
private static String DB_URL="jdbc:mysql://192.168.198.128:3306/cloud_study?useCursorFetch=true";
stmt.setFetchSize(1);
(c)读取数据表大字段(Blog),内存溢出
解决:流方式
InputStrem in = rs.getBinaryStream("blog");
while((tmp=in.read())!=-1){
out.write(tmp);
}
(d)大量数据插入
解决:批处理
stmt.addBatch()/stmt.executeBatch()/stmt.clearBatch()
(e)乱码
(5)事务
void setAutoCommit(boolean autoCommit) :若为false 关闭自动提交即开启事务
void commit() 提交事务
void rollback() 回滚事务
3、Spring JDBC-对JavaJDBC的进一步封装
1)概念
DAO(Data Access Object):数据访问接口
通过接口跟实现(JDBC/mybatis/Hibernate实现)分离方式屏蔽底层实现细节。
JdbcTemplate:Spring JDBC 封装跟业务无关的代码,如打开连接、执行SQL并循环访问结果集、处理异常、清理环境
2)JdbcTemplate常用API
(1)实现方式:
xml方式:
注:BasicDataSource:数据库连接池。
注解方式:
(2)API
//1.查询
int rowCount = this.jdbcTemplate.queryForObject(
"select count(*) from user",Integer.class);
int rowOfNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from user where first_name=?",
Integer.class,"Joe");
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from user where id=?",
new Object[]{1212L},String.class);
User user = this.jdbcTemplate.queryForObject(
"select first_name,last_name from user where id=?",
new Object[]{1212L},new RowMapper<User>(){
public User mapRow(ResultSet rs,int rowNum)throws SQLException{
User user = new User();
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
return user;
}
});
List<User> users = this.jdbcTemplate.query(
"select first_name,last_name from user,
new RowMapper<User>(){
public User mapRow(ResultSet rs,int rowNum)throws SQLException{
User user = new User();
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
return user;
}
});
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from user where id=?",
new Object[]{1212L},String.class);
//2.更新
this.jdbcTemplate.update(
"insert into user(first_name,last_name) values(?,?)",
"Meimei","Han");
this.jdbcTemplate.update(
"update user set last_name = ? where id=?","Li",5276L);
this.jdbcTemplate.update(
"delete from user where id=?",Long.valueOf(userId));
this.jdbcTemplate.execute("create table user (id interger,
first_name varchar(100),last_name varchar(100))");
3)NamedParameterJdbcTemplate - sql参数
函数API
queryForObject(String sql, Map<String,?> paramMap, RowMapper<T> rowMapper)
queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType)
SqlParameterSource 实现类: MapSqlParameterSource/BeanPropertySqlParameterSource
3)异常处理
DataAccessException(unchecked)
4、注解&反射&代理API讲解
1)注解概念与API
(1)注解(Annotation)
Java5引入,核心是对类、方法、变量、参数和包进行标注,通过反射来访问这些标注信息,以此在运行时改变所注解对象的行为。Java注解由内置注解和元注解组成。
(2)元注解
2)反射概念与API
(1)反射(Reflection)
在程序运行时,动态获取类/对象的属性和方法,以及动态调用对象方法的功能。
(2)常用API
反射常用API
方法名 返回值 参数描述
Class.forName(String) 获取类的元信息 当前类文件的具体位置
对象.getClass() 获取类的元信息 无
getDeclaredFields()/getFields() 获取当前类中的所有属性 无
setAccessible(true) 设置当前属性为可见(操作对象属性时设置) True/False
getDeclaredMethods()/getMethods() 获取类所有方法 无
invoke(obj) 通过反射执行方法 类的元信息
getAnnotation(class) 获取注解 需要获取注解的Class
(3)注解代码示例
annotation
3)代理
(1)代理模式(proxy)为其他对象提供一种代理,在不改变目标对象前提下,扩展目标对象功能。
(2)分类-静态代理
被代理对象和代理对象必须实现相同接口或继承相同父类。
缺点:接口或父类的抽象方法必须实现,维护成本高。
(3)分类-静态代理
代理类在程序运行时创建代理对象的方式,基础是利用反射。
核心方法:
Proxy.newProxyInstance(目标对象.getClass().getClassLoader(), 目标对象.getClass().getInterfaces(), InvocationHandler);
InvocationHandler.invoke()
package com.study.proxydemo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理
public class kaneproxy implements InvocationHandler {
private Object target;
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
doSomethigBefore();
Object obj = method.invoke(target, args);
doSomethigAfter();
return obj;
}
private void doSomethigAfter() {
System.out.println("before");
}
private void doSomethigBefore() {
System.out.println("after");
}
}