自己理解了东西是一回事儿。自己会,但是阐述东西是真的不好讲啊。T_T
主要讲JDBC,事务顺便讲一讲,通篇看完就理解了
连接池,Apache,德鲁伊内容很多另外再写
为了逻辑,有的地方至知识可能会重复一点点,重复看看加深印象
第三部分JDBC编程步骤参考自:https://blog.csdn.net/Jungle_Rao/article/details/81274720。
文章目录
- java基础
- 一、JDBC概述
- 1、JDBC背景
- 2、JDBC介绍
- 3、小结
- 二、JDBC与JDBC驱动
- 三、JDBC编程步骤
- 1、装载相应数据库的JDBC驱动并进行初始化
- 2、建立JDBC和数据库之间的Connection连接
- 3、创建Statement或者PreparedStatement接口,执行SQL语句
- 4、处理和显示结果(ResultSet结果集)
- 5、释放资源
- 四、获取数据库连接5种方式
- 1、方式1
- 2、方式2
- 3、方式3
- 4、方式4
- 5、方式5
- 五、JDBC类介绍
- 1、DriverManager:驱动管理类
- 作用一:注册驱动
- 作用二:获得连接
- 2、Connection:与数据库连接的对象
- 作用一:创建执行SQL语句的对象
- 作用二:管理事务
- 3、Statement:执行SQL
- 作用一:执行SQL
- 作用二:执行批处理
- 4、ResultSet:结果集
- 作用:结果的遍历
- 六、JDBC常见问题
- 1、驱动版本问题
- 2、中文乱码问题
- 七、JDBC常见异常错误
- 1、java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
- 2、Unknown database mydb;
- 3、Access denied for user ‘root123’@‘localhost’ (using password: YES)
- 4、Table ‘py-school-db.mydb’ doesn’t exist
- 八、事务
- 1、看一个业务需求
- 2、分析出现的问题
- 3、解决之道
- 4、事务
- 九、批处理
- 1、批处理基本介绍
一、JDBC概述 1、JDBC背景
早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!
2、JDBC介绍- JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的代码都是针对MySQL数据库实现的。
- JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。(面向接口编程)
- JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
- java程序员使用JDBC,可以连接任何提供了JDBC 驱动程序的数据库系统,从而完成对数据库的各种操作。
- 简单说JDBC可做三件事:与数据库建立连接、发送 操作数据库的语句并处理结果。
- JDBC原理图
JDBC API
提供者:Sun公司
内容:供程序员调用的接口与类,集成在java.sql和javax.sql包中,如:
1.DriverManager类 作用:管理各种不同的JDBC驱动
2.Connection接口
3.Statement接口
4.ResultSet接口
JDBC驱动
提供者:数据库厂商
作用:负责连接各种不同的数据库
java 程序员
JDBC对Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型。
三者的关系
SUN公司是规范制定者,制定了规范JDBC(连接数据库规范)
数据库厂商微软、甲骨文等分别提供实现JDBC接口的驱动jar包
程序员学习JDBC规范来应用这些jar包里的类。
JDBC编程步骤:
1、导入专用的jar包
2、加载注册JDBC驱动程序
3、提供链接数据库的URL
4、建立JDBC和数据库之间的连接(用户名、密码)
5、创建Statement或者PreparedStatement接口,执行SQL语句
6、从ResultSet结果集中获取查询的结果
7、释放资源
导入专用的jar包(不同的数据库需要的jar包不同)
访问MySQL数据库需要用到第三方的类,这些第三方的类,都被压缩在一个.Jar的文件里。mysql-connector-java-5.0.8-bin.jar包可以在网上下载,或者在MySQL的安装目录下找到。通常下载到该jar包之后将其放到在项目的lib目录下,在本例就会放在E:projectj2selib 这个位置,然后在eclipse中导入这个jar包。
导包步骤: 右键project->property->java build path->libaries->add
external jars
如果没有完成上述步骤的导包操作,后面会抛出ClassNotFoundException
初始化驱动
通过初始化驱动类com.mysql.jdbc.Driver,该类就在
mysql-connector-java-5.0.8-bin.jar中。如果你使用的是oracle数据库那么该驱动类将不同。
注意:Class.forName需要捕获ClassNotFoundException.
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2、建立JDBC和数据库之间的Connection连接
这里需要提供:
数据库服务端的IP地址:127.0.0.1 (这是本机,如果连接其他电脑上的数据库,需填写相应的IP地址)
数据库的端口号: 3306 (mysql专用端口号)
数据库名称: exam(根据你自己数据库中的名称填写)
编码方式: UTF-8
账号: root
密码: admin(如果你在创建数据库的时候没有使用默认的账号和密码,请填写自己设置的账号和密码)
Connection c = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/exam?characterEncoding=UTF-8", "root", "admin");
Connection是与特定数据库连接回话的接口,使用的时候需要导包,而且必须在程序结束的时候将其关闭。getConnection方法也需要捕获SQLException异常。
因为在进行数据库的增删改查的时候都需要与数据库建立连接,所以可以在项目中将建立连接写成一个工具方法,用的时候直接调用即可:
public static Connection getConnection(){
Connection conn = null;
try {
//初始化驱动类com.mysql.jdbc.Driver
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/exam?characterEncoding=UTF-8","root", "admin");
//该类就在 mysql-connector-java-5.0.8-bin.jar中,如果忘记了第一个步骤的导包,就会抛出ClassNotFoundException
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
3、创建Statement或者PreparedStatement接口,执行SQL语句
使用Statement接口
Statement接口创建之后,可以执行SQL语句,完成对数据库的增删改查。其中
,增删改只需要改变SQL语句的内容就能完成,然而查询略显复杂。在Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点,具体在下文中的对比中介绍。所以Statement在实际过程中使用的非常的少,所以具体的放到PreparedStatement那里给出详细代码。
字符串拼接方式的SQL语句是非常繁琐的,中间有很多的单引号和双引号的混用,极易出错。
Statement s = conn.createStatement();
// 准备sql语句
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
//在statement中使用字符串拼接的方式,这种方式存在诸多问题
s.execute(sql);
System.out.println("执行插入语句成功");
使用PreparedStatement接口
与 Statement一样,PreparedStatement也是用来执行sql语句的与创建Statement不同的是,需要根据sql语句创建PreparedStatement。除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接。
给数据库中添加课程: (以下代码中最后关闭资源的两个方法 DbUtil.close(pstmt); DbUtil.close(conn); 和上面的建立连接的方法是一样的,是在工具类中封装一下方法,代码下面有)
public void addCourse(String courseName){
String sql = "insert into t_course(course_name) values(?)";
//该语句为每个 IN 参数保留一个问号(“?”)作为占位符
Connection conn = null; //和数据库取得连接
PreparedStatement pstmt = null; //创建statement
try{
conn = DbUtil.getConnection();
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, courseName); //给占位符赋值
pstmt.executeUpdate();//执行
}catch(SQLException e){
e.printStackTrace();
}finally{
DbUtil.close(pstmt);
DbUtil.close(conn);//必须关闭
}
}
对数据库中的课程进行修改:
public void delCourse(int courseId){
String sql = "delete from t_course where course_id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtil.getConnection();
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setInt(1, courseId);
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
DbUtil.close(pstmt);
DbUtil.close(conn); //必须关闭
}
}
对数据库中的课程进行修改:
public void modifyCourse(int courseId,String courseName){
String sql = "update t_course set course_name =? where course_id=?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtil.getConnection();
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, courseName); //利用Preparedstatement的set方法给占位符赋值
pstmt.setInt(2, courseId);
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
DbUtil.close(pstmt);
DbUtil.close(conn); //必须关闭
}
}
小结1:
由上面的增删改程序可以看出,他们的代码都是大同小异的,主要是SQL语句存在差异,其他的地方几乎是完全相同的。
小结2:
使用PreparedStatement时,他的SQL语句不再采用字符串拼接的方式,而是采用占位符的方式。“?”在这里就起到占位符的作用。这种方式除了避免了statement拼接字符串的繁琐之外,还能够提高性能。
每次SQL语句都是一样的,java类就不会再次编译,这样能够显著提高性能。
String sql = "update t_course set course_name =? where course_id=?";
后面需要用到PreparedStatement接口创建的pstmt的set方法给占位符进行赋值。注意一点,这里的参数索引是从1开始的。
pstmt = (PreparedStatement) conn.prepareStatement(sql); //利用Preparedstatement的set方法给占位符赋值 pstmt.setString(1, courseName); pstmt.setInt(2, courseId); pstmt.executeUpdate();
增删改都使用pstmt.executeUpdate();进行SQL语句的提交 ,查询会有所不同
小结3:
在添加的过程的,如果添加的数据量比较大的话,可以用批量添加。 PreparedStatement接口提供了相应的批量操作的方法。
小结4:为什么PreparedStatement能防止sql注入呢?
因为sql语句是预编译的,而且语句中使用了占位符,规定了sql语句的结构。用户可以设置"?"的值,但是不能改变sql语句的结构,因此想在sql语句后面加上如“or 1=1”实现sql注入是行不通的。
实际开发中,一般采用PreparedStatement访问数据库,它不仅能防止sql注入,还是预编译的(不用改变一次参数就要重新编译整个sql语句,效率高),此外,它执行查询语句得到的结果集是离线的,连接关闭后,仍然可以访问结果集。
for(int i=1;i<100;i++){
pstmt.setInt(1,8000+i);
pstmt.setString(2,"赵_"+i);
pstmt.addBatch();
//批量更新
if(i%10==0){
pstmt.executeBatch();
}
}
查询操作:结果集
public ListfindCourseList(){ String sql = "select * from t_course order by course_id"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; //创建一个集合对象用来存放查询到的数据 List courseList = new ArrayList<>(); try { conn = DbUtil.getConnection(); pstmt = (PreparedStatement) conn.prepareStatement(sql); rs = (ResultSet) pstmt.executeQuery(); while (rs.next()){ int courseId = rs.getInt("course_id"); String courseName = rs.getString("course_name"); //每个记录对应一个对象 Course course = new Course(); course.setCourseId(courseId); course.setCourseName(courseName); //将对象放到集合中 courseList.add(course); } } catch (SQLException e) { // TODO: handle exception e.printStackTrace(); }finally{ DbUtil.close(pstmt); DbUtil.close(conn); //必须关闭 } return courseList; }
4、处理和显示结果(ResultSet结果集)查询操作使用executeQuery()进行更新。
执行查询语句,并把结果集返回给集合ResultSet
ResultSet rs = s.executeQuery(sql);
利用While(ResultSet.next()){…}循环将集合ResultSet中的结果遍历出来。
ResultSet.getXX(); 这里的get方法的括号里面可以填属性值,如下图代码中的course_id,还可以填该属性在数据表中的列号,从1开始编码 ,例如:course_id在我的t-courses数据表中位于第一列,所以执行get方法的时候,我除了代码段中写法外,还可以这样写int courseId = rs.getInt(1); 但是不推荐使用列号的这种方式,因为一段数据表中个属性值得顺序发生变化,就会导致这里出错,而使用属性名则不会出现这样的问题。
while (rs.next()){
int courseId = rs.getInt("course_id");
String courseName = rs.getString("course_name");
//每个记录对应一个对象
Course course = new Course();
//在我的项目中创建了course类,其中定义了set方法,
//所以这里将查询到的值传给了course,也可以直接打印到控制台
course.setCourseId(courseId);
course.setCourseName(courseName);
//将对象放到集合中
courseList.add(course);
}
还有一点需要说明的是:
因为在我的项目中创建了course类,其中定义了set方法,所以这里将查询到的值传给了course,你也可以直接用打印语句将CourseId和CourseName打印到控制台。
course.setCourseId(courseId); course.setCourseName(courseName);5、释放资源
在JDBC编码的过程中我们创建了Connection、ResultSet等资源,这些资源在使用完毕之后是一定要进行关闭的。关闭的过程中遵循从里到外的原则。因为在增删改查的操作中都要用到这样的关闭操作,为了使代码简单,增加其复用性,这里我将这些关闭的操作写成一个方法和建立连接的方法一起放到一份工具类中。
//封装三个关闭方法
public static void close(PreparedStatement pstmt){
if(pstmt != null){ //避免出现空指针异常
try{
pstmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public static void close(ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
小结1:Statement和PreparedStatement的异同及优缺点
同:两者都是用来执SQL语句的
异:PreparedStatement需要根据SQL语句来创建,它能够通过设置参数,指定相应的值,不是像Statement那样使用字符串拼接的方式。PreparedStatement的优点:
1、其使用参数设置,可读性好,不易记错。在statement中使用字符串拼接,可读性和维护性比较差。
2、其具有预编译机制,性能比statement更快。 3、其能够有效防止SQL注入攻击。
小结2:execute和executeUpdate的区别
相同点:二者都能够执行增加、删除、修改等操作。 不同点:
1、execute可以执行查询语句,然后通过getResult把结果取出来。executeUpdate不能执行查询语句。
2、execute返回Boolean类型,true表示执行的是查询语句,false表示执行的insert、delete、update等。executeUpdate的返回值是int,表示有多少条数据受到了影响
小结3:操作流程图
- 获取Driver类实现对象,直接使用com.mysql.jdbc.Driver,属于静态加载
- 连接方式简单,灵活性差,依赖性强。new出Driver对象。
//方式 1
@Test
public void connect01() throws SQLException {
Driver driver = new Driver(); //创建 driver 对象
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
2、方式2
- 使用反射加载 Driver 类 , 动态加载,更加的灵活,减少依赖性,代码中不出现第三方API
//方式 2
@Test
public void connect02() throws ClassNotFoundException,
IllegalAccessException, InstantiationException, SQLException {
//使用反射加载 Driver 类 , 动态加载,更加的灵活,减少依赖性
Class> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance(); //newInstance生成实例
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式 2=" + connect);
}
3、方式3
- 使用 DriverManager 替代 driver 进行统一管理
- DriverManager()是一个类,并非接口
//方式 3 使用 DriverManager 替代 driver 进行统一管理
@Test
public void connect03() throws IllegalAccessException,
InstantiationException, ClassNotFoundException, SQLException {
//使用反射加载 Driver
Class> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建 url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
DriverManager.registerDriver(driver);
//注册 Driver 驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);
}
4、方式4
这种方式使用最多
//方式 4: 使用 Class.forName 自动完成注册驱动,简化代码
//这种方式获取连接是使用的最多,推荐使用
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了 Driver 类
//在加载 Driver 类时,完成注册
Class.forName("com.mysql.jdbc.Driver");
//创建 url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root"; String password = "hsp";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第 4 种方式~ " + connection);
}
为什么可以自动注册?看源码
//源码:
//1. 反射会加载类,静态代码块,在类加载时,会执行一次.
//2. DriverManager.registerDriver(new Driver());
//3. 因此注册 driver 的工作已经完成 register就不用写了
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
为什么for.name()不写也对?
5、方式5Java提供了SPI机制,用户可以自行配置类,JDBC高版本驱动就都引入了这个支持。如果用户使用了Class.forName方式就自己指定了驱动,如果未写这句话,则Java自动去meta-INF/services/java.sql.Driver文件中找启动类。
- 在方式 4 的基础上改进,增加配置文件,让连接 mysql 更加灵活
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式 5 " + connection);
}
五、JDBC类介绍 1、DriverManager:驱动管理类 作用一:注册驱动1.将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
2. properties最好部署在src下面,不要部署在整个项目下面此种方式的好处?
(1)实现了数据域代码的分离,实现了解耦。
(2)如果需要修改配置文件信息,就可以避免重新打包
是实际开发中一般不会使用这个方法完成驱动的注册。
上面讲过的反射,类加载,静态代码执行,java的SPI机制
getConnection()方法,获取与数据库的连接
url :与数据库连接的路径 user :与数据库连接的用户名 password :与数据库连接的密码 主要关注的是url的写法: jdbc:mysql://localhost:3306/web_test3 jdbc :连接数据库的协议 mysql :是jdbc的子协议 localhost :连接的MySQL数据库服务器的主机地址。(连接是本机就可以写成localhost),如果连接不是本机的,就需要写上连接主机的IP地址。 3306 :MySQL数据库服务器的端口号 web_test3 :数据库名称2、Connection:与数据库连接的对象 作用一:创建执行SQL语句的对象
执行SQL语句对象:
Statement :执行SQL
CallableStatement :执行数据库中存储过程
PreparedStatement :执行SQL.对SQL进行预处理。解决SQL注入漏洞。
1.将此连接的自动提交模式设置为给点状态,设为false取消自动提交
//返回void setAutoCommit(boolean autoCommit)
2.使所有上一次提交/回滚后进行的更改称为持久更改,并释放此Connection对象当前持有的所有数据库锁
//返回void commit()
3.取消在当前事务中的进行的所有更改,并释放此Connection对象当前持有店的所有数据库锁
//返回void rollback()3、Statement:执行SQL
- Statement对象,用于执行惊天SQL语句并返回其生成的结果的对象
- 在连接建立后,需要对数据库进行访问,执行命令或者是SQL语句,可以通过
(1)Statement[存在SQL注入风险]
(2)Pre怕热Statement[预处理]
(3)CallableStatement[存储过程]- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
- 要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代Statement就可以了
- 利用SQL中’单撇是字符串的结束符,or只要一个条件成立其它就不用再判断,而恶意造成sql查询失效,本应该只展示一条数据,结果全部展现
- 大家试想如果是一个财务表,本你只能看自己的信息,结果你看了所有人的信息。(信息,内容,流量这是都是money?安全?)
PreparedStatement()
- PreparedStatement执行的Sql语句中的参数用问号(?)来表示,代用PreparedStatement对象的setXxx()方法来设置这些参数
- setXxx方法有两个参数,第一个参数是要设置的SQL语句中的参数索引(从1开始),第二个是设置的SQL语句中的参数的值
- 调用executeQuery(),返回ResultSet对象
- 调用executeUpdate():执行更新,包括,增,删,修改
预处理的好处
作用一:执行SQL
- 不在使用+拼接SQL语句,减少语法错误
- 有效的解决了SQL注入的问题
- 大大减少了编译的次数,效率更高
1.执行给定的SQL语句,该语句可能返回多个结果
//返回boolean execute(String sql)
2.执行给定的SQL语句,该语句返回单个ResultSet对象
//返回ResultSet executeQuery(String sql)
3.执行给定的SQL语句,该语句可能为INSERT,UPDATE或DELETE语句,或者不返回任何内容的SQL语句(如SQL DDL 语句)
//返回int executeUpdate(String sql)作用二:执行批处理
1.将给定的SQL命令添加到此Statement对象的当前命令列表中
//返回void addBatch(String sql)
2.清空此Statexent对象的当前SQL命令列表。
//返回void clearBatch()
3.将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组
//返回int[]数组 executeBatch()4、ResultSet:结果集 作用:结果的遍历
- 通过select()语句的查询结果
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
将光标移动下一行
//返回boolean next()
JDBC的相关API小结
不同版本的mysql需要不同版本的驱动
Mysql5.0x mysql-connector-java-5.1.32.jar Mysql8.0x mysql-connector-java-8.0.21.jar
- Driver变成了: com.mysql.cj.jdbc.Driver,中间多了cj
- url必须加时区参数: serverTimezone=Asia/Shanghai
url增加参数:characterEncoding=utf8防止中文乱码
String url ="jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";七、JDBC常见异常错误 1、java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
错误原因:
1)jar没有导入,没有builder path
2)Class.forName(“com.mysql.jdbc.Driver”); 字符串拼写错误
2、Unknown database mydb;错误原因:
数据库名称拼写错误
3、Access denied for user ‘root123’@‘localhost’ (using password: YES)错误原因:
数据库用户名或者密码错误
4、Table ‘py-school-db.mydb’ doesn’t exist错误原因:
表不存在,也可能表名写错了
八、事务 1、看一个业务需求模拟经典的转账业务,A给B打钱,A打钱,B收钱
2、分析出现的问题①A——>B A给B打钱不能断 断了怎么样?
②A打出去了由于,某种不可知的原因,系统崩溃,不运行了,操作断了
③导致:B收钱的代码不执行了
④最终结果就是:A把钱打出去了,B没收到钱
⑤出现问题了
把A打钱,B收钱捆绑为原子性操作,要么全部执行成功,要么全部执行失败
成功了okkk打钱成功,失败了操作全部失效取消
引出事务,事务将这些操作捆绑为原子性
我自己理解: 事务就是将一些操作捆绑为原子性
1)JDBC程序中导尿管一个Connection对象创建时,默认情况下是自动提交事务:每执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚
2)JDBC程序中为了让多个SQL与居家作为一个整体执行,需要使用事务
3)调用Connection 的 setAutoCommit(false)可以取消自动提交事务
4)在所有的SQL语句都执行成功后,调用Connection 的 commit(); 方法提交事务
5)在其中某个操作事变或出现异常时i,调用Connection 的 rollback(); 方法回滚事务
数据库表
不使用事务处理转账
//没有使用事务.
@Test
public void noTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个 sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建 PreparedStatement 对象
try {
connection = JDBCUtils.getConnection();
// 在默认情况下,connection 是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第 1 条
sql int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第 3 条 sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
使用事务处理转账
//事务来解决
@Test
public void useTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个 sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建 PreparedStatement 对象
try {
connection = JDBCUtils.getConnection();
// 在默认情况下,connection 是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //开启了事务
preparedStatement = connection.prepareStatement(sql); preparedStatement.executeUpdate(); // 执行第 1 条 sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第 3 条 sql
connection.commit(); //这里提交事务
} catch (SQLException e) {
//这里我们可以进行回滚,即撤销执行的 SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的 sql");
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
九、批处理
1、批处理基本介绍
1)当需要成批插入或者更新记录时。可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独处理更有效率
2)JDBC的批量处理语句包括下面的方法:
addBatch():添加需要批量处理的SQL语句或者参数
executeBatch():执行批量处理语句
clearBatch():清空批处理包的语句
3)JDBC连接Mysql时,如果要使用批处理的功能,请在URL中加参数?
rewriteBatchedStatements=true
4)批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,有减少运行次数,效率,性能大大提高
5)注意:需要修改配置文件
jdbc.propertiesurl=jdbc:mysql://localhost:3306/数据库?rewriteBatchedStatements=true
//传统方法,添加 5000 条数据到 admin2
//传统方法,添加 5000 条数据到 admin2
@Test
public void noBatch() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {
//5000 执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
//使用批量方式添加数据
//使用批量方式添加数据
@Test
public void batch() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000 执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
//将 sql 语句加入到批处理包中 -> 看addBatch源码
preparedStatement.addBatch();
//当有 1000 条记录时,在批量执行
if((i + 1) % 1000 == 0) {//满 1000 条 sql
preparedStatement.executeBatch(); //清空一把
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
将 sql 语句加入到批处理包中 -> 看addBatch源码*
//1. 第一就创建 ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的 sql 语句
//3. 当 elementData 满后,就按照 1.5 扩容
//4. 当添加到指定的值后,就 executeBatch
//5. 批量处理会减少我们发送 sql 语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues,this.parameterStreams,
this.isStream, this.streamLengths, this.isNull));
}
}



