JDBC
JDBC理解
概念
- Java Database Connectivity Java连接数据库的技术 独立于数据库系统、通用的存取数据的一组接口(API)
本质
- 独立于数据库系统、通用的存取数据的一组接口(API)
作用
- JDBC为连接不同数据库提供 统一路径,为开发者屏蔽了细节问题
- 开发者只需要面向这一组API(接口)即可,提高开发效率
- 连接不同的数据库,需要不同的数据库厂商提供不同的驱动即可
JDBC快速入门
涉及的API:(java.sql.* 或 javax.sql.*)
- DriverManager类 管理不同的驱动
- Connection 接口 应用和数据库的连接
- Statement 接口 用于执行sql语句
- ResultSet 接口 保存查询的结果
增删改查使用
-
注册驱动(加载驱动)
- Cliass.forName("com.mysql.jdbc.Driver");
-
获取连接对象
- Connection con = DriverManager.getConnection("jdbc:mysql://IP地址:端口号/库名", "用户名", "密码");
- url:要连接数据的地址 如果你连接地址是本地并且端口是默认的3306 此时ip+端口可以省略
-
创建命令对象(创建可执行对象)
- Statement st = con.createStatement();
-
执行并处理结果
- ResultSet rs = st.executeQuery(sql);
查询 - int row = st.executeUpdate(sql);
增删改
- ResultSet rs = st.executeQuery(sql);
-
释放资源(先开后关原则)
- rs.close();
- st.close();
- con.close();
junit单元测试
概述
- JUnit是一个Java语言的单元测试框架。它由Kent(肯特) Beck(贝克)和Erich(埃里希) Gamma(伽马)建立,逐渐成为源于Kent Beck的sUnit(苏尼特)的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
- JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework) Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。
junit使用
- 把junit4.x的测试jar,添加到该项目中来
- 定义一个测试类,测试类的名字:xxxTest
- 注意:方法必须是public修饰,无返回值的,不能被static修饰,该方法上贴有@Test注解
常见注解
- @Before:在每个测试方法执行之前需要执行的方法
- @After:在每个测试方法执行之后需要执行的方法
- @BeforeClass:在所有的测试方法执行之前最先执行并且只执行一次
被测试的方法必须使用static来修饰 - @AfterClass:在所有的测试方法执行之后最后执行并且只执行一次
被测试的方法必须使用static来修饰 - @Test:要执行的测试方法
JDBC优化之连接池
连接池简介
-
数据库连接是一种关键的有限的昂贵的资源,传统数据库连接每发出一个请求都要创建一个连接对象,使用完直接关闭不能重复利用;
-
关闭资源需要手动完成,一旦忘记会造成内存溢出;
请求过于频繁的时候,创建连接极其消耗内存;
而且一旦高并发访问数据库,有可能会造成系统崩溃。
为了解决这些问题,我们可以使用连接池。 -
连接池原理
数据库连接池负责分配、管理和释放数据库连接,它的核心思想就是连接复用,通过建立一个数据库连接池,这个池中有若干个连接对象,当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。一旦连接池中的连接对象被用完了,判断连接对象的个数是否已达上限,如果没有可以再创建新的连接对象,如果已达上限,用户必须处于等待状态,等待其他用户释放连接对象,直到连接池中有被释放的连接对象了,这时候等待的用户才能获取连接对象,从而操作数据库。这样就可以使连接池中的连接得到高效、安全的复用,避免了数据库连接频繁创建、关闭的开销。这项技术明显提高对数据库操作的性能。
-
连接池优势
- 程序启动时提前创建好连接,免去用户请求时创建,给服务器减轻压力
- 连接关闭时不会直接销毁connection,能够重复利用
- 如果超时设定的连接数量但还没有达到最大值,那么可以再创建
- 空闲了,会默认销毁(释放)一些连接,使系统性能达到最优
-
常见开源连接池
- DBCP:是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在bug,hibernate3已不再提供支持
- C3P0:是一个开源组织提供一个数据库连接池,速度相对较慢,稳定性还可以
- Druid:是阿里提供的数据库连接池,集合DBCP、C3P0优点于一身的高性能的数据库连接池
C3P0连接池
- 导入jar包和配置文件
- 创建连接池
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class C3P0Demo {
//创建连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
@Test
public void findAll() throws SQLException {
//通过连接池获取连接对象
Connection con = dataSource.getConnection();
//创建可执行对象
Statement st = con.createStatement();
//执行并获取结果
String sql = "select * from user";
ResultSet rs = st.executeQuery(sql);
while (rs.next()) {
//获取当前行数据
int uid = rs.getInt("uid");
String name = rs.getString("name");
String sex = rs.getString("sex");
double money = rs.getDouble("money");
String address = rs.getString("address");
System.out.println(uid + "\t" + name + "\t" + sex + "\t" + money + "\t" + address);
}
//释放资源
rs.close();
st.close();
//如果从连接池中获取的连接 调用close()方法会将此连接归还到连接池
con.close();
}
}
Druid连接池
- 导入jar包和配置文件
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DruidDemo {
//定义连接池对象
private static DataSource dataSource;
//最先加载并且只加载一次
static{
try {
//创建持久化属性集合
Properties pro = new Properties();
//{driverClassName=com.mysql.jdbc.Driver,url=jdbc:mysql:///day3,username=root,password=root}
//通过类加载器加载的durid.properties文件 因为这个类加载默认是从src路径下去找资源所以不用写src/路径
InputStream fis =DruidDemo.class.getClassLoader().getResourceAsStream("durid.properties");
//pro.load(new FileInputStream("src/durid.properties"));
pro.load(fis);
//通过工厂创建连接池对象
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void deleteUser() throws SQLException {
//从连接池中获取连接
Connection con = dataSource.getConnection();
//获取可执行对象
Statement st = con.createStatement();
String sql = "delete from user where uid =3";
//执行并获取结果
System.out.println(st.executeUpdate(sql) > 0 ? "删除成功" : "删除失败");
//释放资源
st.close();
con.close();
}
}
封装JDBCUtils工具类
工具类封装
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCUtils {
static ComboPooledDataSource dataSource = new ComboPooledDataSource();
/*
从连接池获取连接的方法
*/
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/*
* 释放资源
* */
public static void coloseResouces(ResultSet rs, Statement st, Connection con) {
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con!=null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
import com.alibaba.druid.util.JdbcUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.sql.rowset.JdbcRowSet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*
增删改查
*/
public class JDBC_CRUD {
Connection con;
Statement st;
ResultSet rs;
@Before
public void before() throws SQLException {
//通过工具类获取连接对象
con = JDBCUtils.getConnection();
//创建可执行对象
st = con.createStatement();
}
@After
public void after() {
JDBCUtils.coloseResouces(rs,st,con);
}
@Test
public void findAll() throws SQLException {
//执行并获取结果
rs = st.executeQuery("select * from user ");
while (rs.next()) {
int uid = rs.getInt(1);
String name = rs.getString(2);
String sex = rs.getString(3);
double money = rs.getDouble(4);
String address = rs.getString(5);
System.out.println(uid + "\t" + name + "\t" + sex + "\t" + money + "\t" + address);
}
}
@Test
public void insertUser() throws SQLException {
//执行并获取结果
System.out.println(st.executeUpdate("insert into user values(4,'金莲妹妹','女',9000,'王婆家')") > 0 ? "添加成功" : "添加失败");
}
@Test
public void deleteUser() throws SQLException {
System.out.println(st.executeUpdate("delete from user where uid =4") > 0 ? "删除成功" : "删除失败");
}
@Test
public void updateUser() throws SQLException {
System.out.println(st.executeUpdate("update user set name ='西门庆' where uid =3") > 0 ? "修改成功" : "修改失败");
}
}
线程安全问题
-
多线程共享同一资源(同一个连接对象)引发线程安全问题ThreadLocal类
-
理解
- JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
-
原理
- ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程中都有一个 ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
-
使用
- ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
- ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
- ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCUtils {
static ThreadLocal<Connection> tl = new ThreadLocal<>();
static ComboPooledDataSource dataSource = new ComboPooledDataSource();
/*
从连接池获取连接的方法
*/
public static Connection getConnection() throws SQLException {
//从当前线程中获取到连接对象
Connection con = tl.get();
if (con==null) {
//线程中没有绑定连接对象
con = dataSource.getConnection();
//绑定连接到当前线程对象中
tl.set(con);
}
return con;
}
/*
* 释放资源
* */
public static void coloseResouces(ResultSet rs, Statement st, Connection con) {
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con!=null) {
try {
//从当前线程中解绑
tl.remove();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
SQL注入命令优化
造成sql注入原因
- sql语句中包含特殊符号,将整个sql语句改变
解决
- 将Statement替换成PreparedStatment预编译命令对象
原理
- sql语句先编译 使用?作为占位符,将传递的参数如果出现特殊符号,自动转义
PreparedStatment好处
- 避免了字符串和变量的拼接,屏蔽细节问题
- 解决了sql注入问题
- sql语句可以复用
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void closeResources(ResultSet rs, Statement st,Connection con) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con!=null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
事务
事务的概念
- 事务:一组预编译的sql要么全部执行成功,要么都失败
事务特性(ACID)
- 原子性(Atomicity):多个sql构成了一个整体单元、在这个单元内部要么全部执行成功、要么全部执行失败
- 一致性(Consistency):事务执行前和执行的后状态一致的。
- 隔离性(Isolation):多个事务、相互隔离互不干扰
- 持久性(Durability):事务一旦提交、数据将永久的发送改变、不受外界因素干扰
管理自定义事务语句
-
mysql
- begin开启事务 start transaction 开始事务
- commit;提交事务
- rollback;回滚事务
-
jdbc
- Connection:连接对象
- con.setAutoCommit(false)
false:手动提交事务
true:默认 自动提交事务 - con.commit() 提交事务
- con.rollback() 回滚事务
import com.ujiuye.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class AccountDemo {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要转出的金额");
double money = sc.nextDouble();
Account(money);
}
private static void Account(double money) {
String sql = "update account set money =money-? where name =?";
Connection con = null;
PreparedStatement ps1=null;
PreparedStatement ps2=null;
try {
con = JDBCUtils.getConnection();
//开启事务 true自动提交 false 手动提交
con.setAutoCommit(false);
//创建可执行对象
ps1 = con.prepareStatement(sql);
ps1.setDouble(1,money);
ps1.setString(2,"pgone");
int row = ps1.executeUpdate();
if (row > 0) {
System.out.println("转出成功");
} else {
System.out.println("转出失败");
}
//银行断电
//System.out.println(1/0);
sql = "update account set money =money+? where name =?";
ps2 = con.prepareStatement(sql);
ps2.setDouble(1,money);
ps2.setString(2,"lxl");
//执行并获取结果
int row1= ps2.executeUpdate();
if (row1 > 0) {
System.out.println("转入成功");
}else{
System.out.println("转入失败");
}
//提交事务
con.commit();
} catch (Exception e) {
//回滚事务
try {
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
//异常总执行
try {
ps2.close();
ps1.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
批量处理
概述
- JDBC操作数据库的时候,需要一次性插入大量的数据的时候,如果每次只执行一条SQL语句,效率可能会比较低。这时可以使用batch操作,每次批量执行SQL语句,调高效率。
import com.ujiuye.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
批量插入
*/
public class InsertOffcn_Demo02 {
public static void main(String[] args) throws SQLException {
//获取连接
Connection con = JDBCUtils.getConnection();
//创建可执行对象
String s = "insert into offcn values(?,?,?,?)";
PreparedStatement ps = con.prepareStatement(s);
long start = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
ps.setInt(1,i);
ps.setString(2,"zg"+i);
ps.setInt(3,i);
ps.setString(4,"zg"+i+"@qq.com");
//积攒
ps.addBatch();
if (i%2000==0) {
//执行一次
ps.executeBatch();//批量插入
System.out.println("插入了"+i+"条");
}
}
long end = System.currentTimeMillis();
System.out.println("花费了"+(end-start)+"毫秒");
//清除批量插入
ps.clearBatch();
//释放资源
ps.close();
con.close();
}
}

浙公网安备 33010602011771号