一、概述
1.概念:JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API。
2.作用: JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口(大部分)和类组成。
3.JDBC和驱动的关系:
Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。
4.JDBC规范(四个核心):
- DriverManager:驱动管理类;
- Connection:与数据库创建连接的接口,实现类在驱动中;
- Statement:操作数据库SQL语句的接口,实现类在驱动中;
- ResultSet:结果接口,实现类在驱动中;
二、JDBC的入门
1.导入驱动jar包
位置:创建lib目录,用于存放当前项目需要的所有jar包;
导包:右键project->property->java build path->libaries->add external jars;
2.原生开发步骤
1) 注册驱动
2) 获得连接
3) 获得执行sql语句的对象
4) 执行sql语句,并返回结果
5) 处理结果集
6) 释放资源
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取与数据库连接对象
String url = "jdbc:mysql://localhost:3306/db1216";
java.sql.Connection ct = DriverManager.getConnection(url, "root", "root");
//获取SQL语句执行对象
Statement st = ct.createStatement();
//执行SQL语句
ResultSet exe = st.executeQuery("select * from person");
//处理结果集
while(exe.next()) {
int pid = exe.getInt("pid");
String pname = exe.getString("pname");
int page = exe.getInt("page");
String psex = exe.getString("psex");
String format = "%d %s %d %s%n";
System.out.printf(format, pid,pname,page,psex);
}
//释放资源
exe.close();
st.close();
ct.close();
}
console结果:
1 小明 18 男
2 小华 20 男
3 小红 18 女
4 小芳 23 女
5 小亮 17 男
3.工具类 JDBCUtils的编写
示例:
//工具类
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCUtils {
private static String drivername = "com.mysql.jdbc.Driver";
private static String url = "jdbc:mysql://localhost:3306/db1216";
private static String user = "root";
private static String password = "root";
//注册驱动,只需要执行一次,不需要每次创建连接都注册一遍;
static{
try {
Class.forName(drivername);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnect() throws SQLException {
Connection con = DriverManager.getConnection(url, user, password);
System.out.println("数据库连接成功!");
return con;
}
//关闭资源
public static void CloseAll(Connection con,java.sql.Statement st,ResultSet rs) {
try {
if(rs != null) rs.close();
if(st != null) st.close();
if(con != null) con.close();
System.out.println("已退出连接!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//测试类
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
con = JDBCUtils.getConnect();
st = con.createStatement();
rs = st.executeQuery("select * from person");
while(rs.next()) {
int pid = rs.getInt("pid");
String pname = rs.getString("pname");
int page = rs.getInt("page");
String psex = rs.getString("psex");
String format = "%d %s %d %s%n";
System.out.printf(format, pid,pname,page,psex);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.CloseAll(con, st, rs);
}
}
}
console结果:
数据库连接成功!
1 小明 18 男
2 小华 20 男
3 小红 18 女
4 小芳 23 女
5 小亮 17 男
已退出连接!
4.PreparedStatement和Statement
示例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import JdbcUtils.JDBCUtils;
public class demo {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
Statement st = null;
try {
con = JDBCUtils.getConnect();
//使用prepareStatement
String sql = "insert into person(pid,pname,page,psex) values (?,?,?,?)";
pst = con.prepareStatement(sql);
pst.setInt(1, 6);
pst.setString(2, "小苟");
pst.setInt(3, 30);
pst.setString(4, "人妖");
pst.executeUpdate();
//使用statement
String sql2 ="insert into person (pid,pname,page,psex) values (" + 8 +"," + "'小亮'" + "," + 28 +"," +
"'男'" + ")";
st = con.createStatement();
st.executeUpdate(sql2);
System.out.println("数据操作完成!");
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.CloseAll(con, pst, rs);
JDBCUtils.CloseAll(con, st, rs);
}
}
console结果:
数据库连接成功!
数据操作完成!
已退出连接!
Statement与PreparedStatement区别:
- Statement 需要进行字符串拼接,可读性和维护性比较差;PreparedStatement 使用参数设置,可读性好,不易犯错;
- PreparedStatement有预编译机制,性能比Statement更快;
- 防止SQL注入式攻击
5.execute与executeUpdate
execute与executeUpdate区别:
- 相同点:都可以执行增加,删除,修改
- 不同点1:
execute可以执行查询语句 然后通过getResultSet,把结果集取出来 executeUpdate不能执行查询语句;- 不同点2:
execute返回boolean类型,true表示执行的是查询语句,false表示执行的是insert,delete,update等等;
executeUpdate返回的是int,表示有多少条数据受到了影响;
三、JDBC连接池(ConnectionPool)
1.连接池作用
1.1不使用连接池:当有多个线程,每个线程都需要连接数据库执行SQL语句的话,那么每个线程都会创建一个连接,并且在使用完毕后,关闭连接;
缺点:创建连接,关闭连接需要时间,同时也消耗系统资源;
1.2使用连接池:连接池在使用之前,就会创建好一定数量的连接。如果有任何线程需要使用连接,那么就从连接池里面借用,而不是自己重新创建。使用完毕后,又把这个连接归还给连接池供下一次或者其他线程使用。
优点:连接有借有还,效率高;
2.连接池规范
规范:Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口,这样应用程序可以方便的切换不同厂商的连接池。
常见的连接池:DBCP、C3P0;
3.DBCP连接池
DBCP是Apache开发的一个 Java 连接池项目,也是 tomcat 使用的连接池组件;
3.1 导入jar包:
导入commons-dbcp-1.4.jar和commons-pool-1.5.6.jar
3.2 添加配置文件:
- 配置文件名称:*.properties
- 配置文件位置:任意,建议src(classpath/类路径)
- 配置文件内容:properties不能编写中文
3.3 BasicDataSource和BasicDataSourceFactory的开发过程:
不使用配置文件直接使用连接池类(BasicDataSource)的开发步骤:
- 创建连接池对象:使用构造直接new一个对象
- 注册驱动方法:public synchronized void setDriverClassName(String driverClassName)
- 设置数据库URL:public synchronized void setUrl(String url)
- 设置数据库User: public void setUsername(String username)
- 设置数据库Password:public void setPassword(String password)
- 获取连接对象:public Connection getConnection()
- 关闭资源
使用配置文件需要连接池工厂类(BasicDataSourceFactory)的开发步骤:
- 加载配置文件new Properties() .load(InputStream inStream)
- 创建连接池方法(静态方法): public static DataSource createDataSource(Properties properties)
- 获取连接对象:Connection getConnection()
- 关闭资源
3.4 使用配置文件示例:
//DBCPUtils工具类
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class DBCPUtils {
private static DataSource ds = null;
//获取连接池对象,仅需要创建一次连接池对象
static {
Properties config = new Properties();
try {
//配置文件必须要放在src目录下:因为getResourceAsStream()方法就是从src中获取配置文件再转流
config.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));
ds = BasicDataSourceFactory.createDataSource(config);
System.out.println("连接池已发出连接!");
} catch (Exception e) {
e.printStackTrace();
}
}
//从连接池获取连接
public static Connection getConnection() throws SQLException {
Connection con = ds.getConnection();
System.out.println("数据库连接成功!");
return con;
}
//关闭资源
public static void CloseAll(Connection con,java.sql.Statement st,ResultSet rs) {
try {
if(rs != null) rs.close();
if(st != null) st.close();
if(con != null) con.close();
System.out.println("已退出连接!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//测试类
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class test {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet exe = null;
String sql = "select * from person";
try {
con = DBCPUtils.getConnection();
pst = con.prepareStatement(sql);
exe = pst.executeQuery();
while(exe.next()) {
int pid = exe.getInt("pid");
String pname = exe.getString("pname");
String format = "编号: %d 名字:%s%n";
System.out.printf(format,pid,pname);
}
} catch (SQLException e) {
e.printStackTrace();
}
DBCPUtils.CloseAll(con, pst, exe);
}
}
记忆点:获取类对象有3种方式
- Class.forName
- 类名.class
- new 类名().getClass()
4. C3P0连接池
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
3.1 导入jar包:
导入c3p0-0.9.1.2.jar 和 mchange-commons-java-0.2.11.jar
3.2 配置:
添加配置文件c3p0-config.xml到项目src目录下
3.3 开发过程:
连接池类:ComboPooledDataSource
- 配置好xml文件(c3p0连接池是自动会送src目录下加载xml配置文件,不要像DBCP一样需要手动加载)
- 创建连接池对象:直接new一个ComboPooledDataSource()
- 获取连接
- 关闭资源
3.4 示例:
//C3P0Utils工具类
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Utils {
private static ComboPooledDataSource cp = new ComboPooledDataSource();
//从连接池获取连接
public static Connection getConnection() throws SQLException {
Connection con = cp.getConnection();
System.out.println("数据库连接成功!");
return con;
}
//关闭资源
public static void CloseAll(Connection con,java.sql.Statement st,ResultSet rs) {
try {
if(rs != null) rs.close();
if(st != null) st.close();
if(con != null) con.close();
System.out.println("已退出连接!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5.普通连接和连接池的多线程测试:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import DbcpUtils.DBCPUtils;
import JdbcUtils.JDBCUtils;
public class Test {
public static void main(String[] args) {
//测试多线程下,传统方式连接和连接池的性能差异
//创建线程数组用来保存线程对象
Thread[] Th = new Thread[100];
//记录起始时间点
long t1 = System.currentTimeMillis();
//创建100组线程,每个线程都要运行“连接-执行SQL语句-关闭”完整访问数据库操作
for(int i=0;i<100;i++) {
Thread t = new Thread() {
public void run() {
System.out.println(this.getName()+"线程运行");
// SimpleConnect();
PoolConnect();
}
};
t.start();
Th[i] = t;
}
//等待所有线程结束
for (Thread twait : Th) {
try {
twait.join(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//记录结束时间点,并输出结果
long t2 = System.currentTimeMillis();
System.out.println("耗时:"+ (t2-t1) +"ms" );
}
public static void SimpleConnect() {
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
con = JDBCUtils.getConnect();
st = con.createStatement();
rs = st.executeQuery("select * from person");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.CloseAll(con, st, rs);
}
}
public static void PoolConnect() {
Connection con = null;
PreparedStatement pst = null;
ResultSet exe = null;
String sql = "select * from person";
try {
con = DBCPUtils.getConnection();
pst = con.prepareStatement(sql);
exe = pst.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBCPUtils.CloseAll(con, pst, exe);
}
}
}
6.DBUtils工具类库
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
6.1 Dbutils三个核心:
-
QueryRunner类:提供对sql语句操作的API;
构造:
public QueryRunner(DataSource ds);
public QueryRunner();
方法:
int update(String sql, Object… params)
< T > T query(String sql, ResultSetHandler rsh, Object… params) -
ResultSetHandler接口:用于定义select操作后,怎样封装结果集;
主要的实现类:
-
DbUtils类:一个工具类,定义了关闭资源与事务处理的方法;
构造:
6.2 示例:
//JavaBean类
public class Identity implements java.io.Serializable{
private static final long serialVersionUID = -8771783031495068229L;
private int pid;
private String pname;
private int page;
private String psex;
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public String getPsex() {
return psex;
}
public void setPsex(String psex) {
this.psex = psex;
}
}
//测试类
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import c3p0Utils.C3P0Utils;
public class Test {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource ds = C3P0Utils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
// insert(qr);
// delete(qr);
// update(qr);
query(qr);
}
public static void insert(QueryRunner qr) throws SQLException {
String sql = "insert into person (pid,pname,page,psex) values (?,?,?,?)";
Object[] params = {9,"小美",6,"女"};
int u = qr.update(sql, params);
if(u>0) {
System.out.println("已执行插入操作");
}
}
public static void delete(QueryRunner qr) throws SQLException {
String sql = "delete from person where pid = ?";
Object[] params = {7};
int u = qr.update(sql, params);
if(u>0) {
System.out.println("已执行删除操作");
}
}
public static void update(QueryRunner qr) throws SQLException {
String sql = "update person set page = ? where pid = ?";
Object[] params = {40,2};
int u = qr.update(sql, params);
if(u>0) {
System.out.println("已执行插入操作");
}
}
public static void query(QueryRunner qr) throws SQLException {
//ArrayHandler
String sql1 = "select * from person where pid = ?";
ResultSetHandler<Object[]> rsh1 =new ArrayHandler();
Object[] params1 = {6};
Object[] rs1 = qr.query(sql1, rsh1, params1);
for (Object obj : rs1) {
System.out.println(obj + " ");
}
//ArrayListHandler
String sql2 = "select * from person";
ArrayListHandler rsh2 = new ArrayListHandler();
List<Object[]> rs2 = qr.query(sql2, rsh2);
for (Object[] obj : rs2) {
System.out.println(obj[0] +" " +obj[1] + " " + obj[2] + " " + obj[3]);
}
//BeanHandler
String sql3 = "select *from person";
BeanHandler<Identity> bh = new BeanHandler<Identity>(Identity.class);
Object[] params3 = null;
Identity rs3 = qr.query(sql3, bh, params3);
System.out.println(rs3.getPname());
//BeanListHandler
String sql4 = "select *from person";
BeanListHandler<Identity> blh = new BeanListHandler<Identity>(Identity.class);
Object[] params4 = null;
List<Identity> rs4 = qr.query(sql4, blh, params4);
for (Identity id : rs4) {
System.out.println(id.getPname());
}
//ColumnListHandler
String sql5 = "select *from person";
ColumnListHandler<String> clh = new ColumnListHandler<String>("pname");
Object[] params5 = null;
List<String> rs5 = qr.query(sql5, clh,params5);
System.out.println(rs5);
//MapHandler
String sql6 = "select *from person";
MapHandler mh = new MapHandler();
Object[] params6 = null;
Map<String, Object> rs6 = qr.query(sql6, mh, params6);
System.out.println(rs6);
//MapListHandler
String sql7 = "select *from person";
MapListHandler mlh = new MapListHandler();
Object[] params7 = null;
List<Map<String, Object>> rs7 = qr.query(sql7, mlh, params7);
for (Map<String, Object> obj : rs7) {
System.out.println(obj);
}
//ScalarHandler
String sql8 = "select count(*) from person";
ScalarHandler<Long> sh = new ScalarHandler<Long>();
Object[] params8 = null;
Long rs8 = qr.query(sql8, sh, params8);
System.out.println("总行数:"+rs8);
}
}
console结果:
6
小苟
30
人妖
1 小明 18 男
2 小华 40 男
3 小红 18 女
4 小芳 23 女
5 小亮 17 男
6 小苟 30 人妖
8 小亮 28 男
9 小美 6 女
小明
小明
小华
小红
小芳
小亮
小苟
小亮
小美
[小明, 小华, 小红, 小芳, 小亮, 小苟, 小亮, 小美]
{pid=1, pname=小明, page=18, psex=男}
{pid=1, pname=小明, page=18, psex=男}
{pid=2, pname=小华, page=40, psex=男}
{pid=3, pname=小红, page=18, psex=女}
{pid=4, pname=小芳, page=23, psex=女}
{pid=5, pname=小亮, page=17, psex=男}
{pid=6, pname=小苟, page=30, psex=人妖}
{pid=8, pname=小亮, page=28, psex=男}
{pid=9, pname=小美, page=6, psex=女}
总行数:8
四、事务
1.概述
1.1 概念: 事务指的是逻辑上的一组操作(多条sql语句),组成这组操作的各个单元要么全都成功,要么全都失败。
1.2 作用: 保证在一个事务中多次操作要么全都成功,要么全都失败。比如银行转账,要么转账成功要么转账失败,不能出现一方扣款成功,另一方没有收款,那么转账整个过程就可以看成是一个事务;
2.MySql事务
2.1 使用DBUtils示例:
//借助ThreadLocal创建的连接管理类
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import c3p0Utils.C3P0Utils;
public class Test {
public static void main(String[] args) {
Connection con = null;
try {
//获取连接
con = C3P0Utils.getConnection();
//关闭自动提交事务就等于开启事务
con.setAutoCommit(false);
//转账
transfer(con);
//提交事务
con.commit();
System.out.println("转账成功!");
} catch (Exception e) {
System.out.println("转账失败,进行回滚");
//回滚事务
try {
con.rollback();
} catch (SQLException e1) {
System.out.println("回滚失败!");
}
}
}
public static void transfer(Connection conn) throws SQLException {
deduction(conn);
collection(conn);
}
//扣款
public static void deduction(Connection conn) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set total=total-? where name=?";
Object[] params = {10000,"kate"};
qr.update(conn, sql , params);
// System.out.println(1/0);//测试转账过程中出现异常
}
//收款
public static void collection(Connection conn) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set total=total+? where name=?";
Object[] params = {10000,"jack"};
qr.update(conn, sql , params);
}
}
2.2 事务特性:
- 原子性:强调事务的不可分割.多条语句要么都成功,要么都失败;
- 一致性:强调的是事务的执行的前后,数据要保持一致;
- 隔离性:一个事务的执行不应该受到其他事务的干扰;
- 持久性:事务一旦结束(提交/回滚)数据就持久保持到了数据库;
2.3 安全性:
存在的问题:
- 脏读 :一个事务读到另一个事务还没有提交的数据;
- 不可重复读 :一个事务读到了另一个事务已经提交的update的数据,导致在当前的事务中多次查询结果不一致;
- 虚读/幻读 :一个事务读到另一个事务已经提交的insert的数据,导致在当前的事务中多次的查询结果不一致;
设置事务的隔离级别:
- 1 read uncommitted :未提交读.脏读,不可重复读,虚读都可能发生;
- 2 read committed :已提交读.避免脏读.但是不可重复读和虚读有可能发生.(Oracle默认);
- 4 repeatable read :可重复读.避免脏读,不可重复读.但是虚读有可能发生.(MySql默认);
- 8 serializable :串行化的.避免脏读,不可重复读,虚读的发生.;
级别超高,越安全,效率越低;设置隔离级别必须在事务之前;
MySql事务隔离语句:
- 查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
- 更改当前的事务隔离级别:SET TRANSACTION
- ISOLATION LEVEL 四个级别之一;
3.初涉三层架构(3-tier architecture)
3.1 三层架构思想:高内聚、低耦合
3.2 具体分类:持久层(DAO层)、业务层(service层)、表现层(view层)
3.3 示例:
import java.sql.Connection;
import java.sql.SQLException;
import c3p0Utils.C3P0Utils;
public class ConManager {
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public static Connection getCon() throws SQLException {
//获取连接,并保存在当前线程下的局部变量中
Connection con = tl.get();
if(con == null){
con = C3P0Utils.getConnection();
tl.set(con);
}
return con;
}
public static void start() throws SQLException {
//关闭自动提交事务就等于开启事务
ConManager.getCon().setAutoCommit(false);
}
public static void commit() throws SQLException {
//提交事务
ConManager.getCon().commit();
}
public static void rollback() throws SQLException {
//回滚事务
ConManager.getCon().rollback();
}
public static void close() {
try {
ConManager.getCon().close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//DAO层
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import Service.ConManager;
public class Db {
//扣款
public static void deduction(int num,String name) throws SQLException {
Connection conn = ConManager.getCon();
QueryRunner qr = new QueryRunner();
String sql = "update account set total=total-? where name=?";
qr.update(conn, sql , num , name);
// System.out.println(1/0);
}
//收款
public static void collection(int num,String name) throws SQLException {
Connection conn = ConManager.getCon();
QueryRunner qr = new QueryRunner();
String sql = "update account set total=total+? where name=?";
qr.update(conn, sql , num , name);
}
}
//业务层
import java.sql.SQLException;
import Dao.Db;
public class Ser {
public static void transfer(String dname,int num,String cname){
try {
ConManager.start();
//转账
Db.deduction(num,dname);
Db.collection(num,cname);
System.out.println("转账成功");
ConManager.commit();
} catch (Exception e) {
System.out.println("转账失败,进行回滚");
//回滚事务
try {
ConManager.rollback();
} catch (SQLException e1) {
System.out.println("回滚失败!");
} finally {
ConManager.close();
}
}
}
}
//表现层
import Service.Ser;
public class Vi {
public static void main(String[] args) {
int num = 2000;
String dname = "jack";
String cname = "tom";
Ser.transfer(dname,num,cname);
}
}
4.ThreadLocal类
4.1 方法:
T get() :返回当前线程的此线程局部变量的副本中的值
protected T initialValue() :返回此线程局部变量的当前线程的“初始值”
void remove() :删除此线程局部变量的当前线程的值
void set(T value) :将当前线程的此线程局部变量的副本设置为指定的值
static < S > ThreadLocal< S > withInitial(Supplier<? extends S> supplier) :创建线程局部变量
4.2 为什么上例中需要用ThreadLocal?
- 线程安全问题:DBUtils示例在单线程使用中是没有问题的,但是在多线程中,创建的某个数据库连接就可能会被多个线程共用比如某个线程刚开启的连接被另一个线程关闭;
- 解决措施:
1)是否可以使用同步:需要调用数据库连接的地方都进行同步,这样是解决安全问题但是效率太低,因为只能一个线程在使用连接操作数据库,其他线程只能等待;
2)使用ThreadLocal:ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
4.3 ThreadLocal类内部原理:
在这里插入代码片