目录
一、简介
二、JDBC体系结构
三、JDBC核心组件
四、JDBC操作步骤
五、sql注入与PreparedStatement(预状态通道)
sql注入:
PreparedStatement(预状态通道):
六、Java操作多表关系
一对多关系:
多对一关系
双向一对一
多对多
多表关系操作总结:
七、数据库事务
JDBC中事务应用
保存点Savepoints
事务案例
批处理
基于statement实现批处理
基于preparedstatement的批处理
八、反射处理结果集
九、工具类的定义
十、属性文件
十一、连接池
DBCP连接池(了解)
C3P0连接池(了解)
德鲁伊连接池(用的最多)
了解数据库移步:
数据库初级
数据库高级
一、简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。
JDBC库包括通常与数据库使用相关的下面提到的每个任务的API。
- 连接数据库。
- 创建SQL或MySQL语句。
- 在数据库中执行SQL或MySQL查询。
- 查看和修改生成的记录。
二、JDBC体系结构
1、JDBC API:提供应用和程序到JDBC管理器连接。
2、JDBC驱动程序API:支持JDBC管理器到驱动程序连接。
通过Java代码调用JDBC的API ,API调用JDBC Driver Manager 驱动管理器。驱动管理器根据不同的数据库来进行不同的驱动连接。
三、JDBC核心组件
核心组件就是核心的工具类。
1、DriverManager:此类管理数据库驱动程序列表。管理各个不同的数据库的驱动地址。
2、Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用
DriverManager对象来管理这种类型的对象。也就是说,Diver是用来管理数据库的驱动的。
3、Connection:对数据库进行连接,该界面具有用于联系数据库的所有方法。连接对象表示通信上下文,即,与数据库的所有通信仅通过连接对象。
4、statement:对数据进行提交。使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。
5、ResultSet:在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。(比如,我们查询数据后,返回的是一张虚拟表,而这个虚拟表的结果集对应到java类中就是ResultSet类)
6、SQLException:此类处理数据库应用程序中发生的任何错误
四、JDBC操作步骤
一、导入依赖包,大家就自己在网上下载jar包吧(或者私信我,发给你),,我还不会发上来。导入jar包后记得配置变量!
二、注册JDBC驱动程序:此步骤将使JVM将所需的驱动程序实现加载到内存中,以便它可以满足您的JDBC请求。注册驱动程序最常见的方法是使用Java的Class.forName("com.mysql.cj.jdbc.Driver")方法(注:若不是mysql8版本,则去掉“.cj”)
三、获得连接:
String URL = "jdbc:mysql://localhost:3306/yhp2?serverTimezone=UTC"; (不同的数据库地址不同,详情见下表)注:hostname是你要链接的数据库的ip,本机为locahost,后面的databasename也要换成你要用的数据库名称。
String USER = "username";
String PASS = "password"
Connection conn = DriverManager.getConnection(URL, USER, PASS);
三、创建连接对象:调用DriverManager对象的getConnection()方法来建立实际的数据库连接。
四、对数据表进行,增删改查操作。
五、释放资源:用完后要close,需要明确的关闭所有数据库资源,而不依赖于jvm的垃圾回收。
源码演示:
package com.yuyu;
import java.sql.*;
public class demo1 {
public static void main(String[] args) {
//我们将下面用到的变量都挪到了这里,方便管理,当然,不想挪就不挪~
Connection connection = null; //连接
Statement statement = null; //状态通道
ResultSet resultSet = null; //结果
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String userName = "root";
String passWord = "Wxy180701";
//连接mysql8的驱动地址
String url = "jdbc:mysql://localhost:3306/yhp2?serverTimezone=UTC";
connection = DriverManager.getConnection(url, userName, passWord);
//定义sql,创建状态通道(进行sql语句的发送)
statement = connection.createStatement();
//执行查询
resultSet = statement.executeQuery("select * from student2");
//取出结果集信息
while(resultSet.next()){ //判断是否有下一条数据
//取出数据:resultSet.getxxx("列名"); xxx表示数据结构
System.out.println("学生编号:"+resultSet.getInt("id")
+",学生姓名:" +resultSet.getString("name"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//finally块用来关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (resultSet != null) {
resultSet.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
这一段源码,演示的是对数据库的数据进行查询的操作,下面我们来演示增删改的操作:
其实我们会发现,差别就在于statement调用的方法不一样罢了。
由于查询是要返回结果集,所以调用的是executeQuery(String SQL语句):
ResultSet executeQuery(string sql语句):返回一个ResultSet对象。当您希望获得结果集时,请使用此方法,就像使用SELECT语句一样。
当对数据库进行增删改时,我们调用executeUpdate(String SQL语句):
int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如INSERT,UPDATE或DELETE语句。
了解:boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQL DDL语句或需要使用真正的动态SQL时。
package com.yuyu;
import java.sql.*;
public class Demo3 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
int result = 0;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String name = "root";
String pw = "Wxy180701";
String url = "jdbc:mysql://localhost:3306/haha?serverTimezone=UTC";
connection = DriverManager.getConnection(url, name, pw);
statement = connection.createStatement();
//修改
result = statement.executeUpdate("update student5 set stuname='丽丽' where stuname = '丽萨'");
if (result>0){
System.out.println("修改成功");
}else System.out.println("执行失败!");
/* //新增
int insert = statement.executeUpdate("insert into student5 values('haha','女',20,6)");
if (insert>0) System.out.println("新增成功");
else System.out.println("新增失败");*/
//删除
int delete = statement.executeUpdate("delete from student5 where stuname='haha' ");
if (delete>0) System.out.println("删除成功");
else System.out.println("删除失败");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection !=null){
connection.close();
}
if (statement != null){
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
五、sql注入与PreparedStatement(预状态通道)
sql注入:
指的是,用户提交了一些东西时,在其中加入sql片段(恶意的),最终达到欺骗服务器执行的恶意的sql命令。它利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库。
下面我们通过一个例子来讲解,比如用户登录,需要用户名和密码。
我们设计的语句是:传入一个用户名,和密码。两者匹配(两者都属于同一用户)即登录成功。
statement.executeQuery("select * from user2 where username='"+name+"' and passWord="+pw);
sql注入即是:一个用户在输入密码时,传入sql语句,也可以登录的情况。(通过漏洞的恶意操作)此时,不管我们输入什么name,都可以登录到数据库中!
那么如何来防止这种情况发生,保证数据库的安全性呢?
PreparedStatement(预状态通道):
预状态通道有效的避免了sql注入,该PreparedStatement的接口扩展了Statement接口。
eg:
PreparedStatement pstmt = null; //创建预状态通道对象
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL); //将sql语句传入对象
sql语句中的 ?为占位符,此语句可以让你动态地提供参数。
将变量替代?占位符即可。
注意:替换占位符时,要使用与状态通道的对象调用setxxx //xxx为变量类型(第几个占位符,要替换的变量名称) 方法。
源码:
package com.yuyu;
import java.sql.*;
public class Demo6copy5 {
public static void main(String[] args) {
Connection connection = null; //连接
PreparedStatement pps = null; //状态通道
ResultSet resultSet = null; //结果
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String userName = "root";
String passWord = "Wxy180701";
//连接mysql8的驱动地址
String url = "jdbc:mysql://localhost:3306/haha?serverTimezone=UTC";
connection = DriverManager.getConnection(url, userName, passWord);
//定义sql,创建状态通道(进行sql语句的发送)
String sql = "select * from user2 where username=? and passWord=?";
pps = connection.prepareStatement(sql);
String name = "小红";
String pw = "111";
//将?替换成变量
pps.setString(1,name);
pps.setString(2,pw);
resultSet = pps.executeQuery();
if (resultSet.next()){
System.out.println("登录成功!");
}else System.out.println("登录失败。");
//取出结果集信息
while(resultSet.next()){ //判断是否有下一条数据
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (pps!= null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
六、Java操作多表关系
四种:双向一对一,一对多,多对一,多对多
多表关系处理数据:
(1) 数据库通过外键建立两表关系
(2) 实体类通过属性的方式建立两表关系
实体类要求:类名=表名,列名=属性名
一对多关系:
我们先创建两个表:
老师表:
学生表:
一个老师可以对应多个学生,所以老师为“多”方,学生为“一”方。
多表关系在java中,通过实体类来建立关系
一、我们先创建“学生类”和“老师类”,并创建相应的属性:
创建完后,创建get set方法将这两个类封装起来!!!
二、我们知道数据表中,通过主外键来建立多表关系,那么在类中,是通过属性来创建关系。
原则:在“一方”创建存储“多方”数据的集合。
这样每一个老师中都包含一个学生集合。从而实现“一对多”的关系。
三、创建一个接口,用来在里面定义方法
package dao;
import bean.Teacher;
public interface Teacherdao {
//接口用来定义操作的方法
//1、定义一个根据老师id查询老师信息(包括老师所带的学生信息)
public Teacher findbyId(int tid);
}
四、写一个实现类来实现这个接口:(注释很详细,请仔细阅读!)
package dao.impl;
import bean.Teacher;
import bean.student;
import dao.Teacherdao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class TeacherdaoImpl implements Teacherdao {
@Override
//实现方法:
public Teacher findbyId(int tid) {
//在实现类中操作数据库
Connection connection = null;
PreparedStatement pss = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
String userName = "root";
String pw = "Wxy180701";
connection = DriverManager.getConnection(url, userName, pw);
String sql = "SELECT *from student s,teacher t where s.teacherid=t.tid and t.tid=?";
pss = connection.prepareStatement(sql);
pss.setInt(1,tid);
resultSet = pss.executeQuery();
//调用一次findbyId方法是传一个tid,所以只用创建一个老师对象
Teacher teacher = new Teacher();
//写一个集合,用来存学生的信息
List<student> students = new ArrayList<>();
while (resultSet.next()){
//每循环一次都是遍历一行数据
teacher.setTid(resultSet.getInt("tid"));
teacher.setTname(resultSet.getString("tname"));
//每一行数据都是一个新的学生,所以将new学生对象放在循环里
student student = new student();
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
//每次循环,将学生的信息从库中提取出来,封装在一个学生对象里,再将对象存入集合中
students.add(student);
}
//老师将学生集合存进去
teacher.setStudentList(students);
return teacher;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
}
try {
if(connection!=null) connection.close();
if (pss!=null) pss.close();
if (resultSet!=null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
测试类测试一下:
package test;
import bean.Teacher;
import bean.student;
import dao.Teacherdao;
import dao.impl.TeacherdaoImpl;
import java.util.List;
public class Demo1 {
public static void main(String[] args) {
//用接口接收实现类的对象
Teacherdao dao = new TeacherdaoImpl();
//调用方法,返回值为teacher类对象
Teacher teacher = dao.findbyId(1);
//得到对象后,用对象的get方法获取属性的值
System.out.println("老师:"+teacher.getTname());
//得到学生集合,遍历输出
List<student> studentList = teacher.getStudentList();
for (student student : studentList) {
System.out.println("\t 学生姓名:"+student.getStuname());
}
}
}
测试结果:
与数据表中的查询结果一致!
多对一关系
我们站在学生的角度看,就是多个学生对应一个老师。我们知道,在一对多的关系中,是在“一方”创建存储“多方”数据的集合。那么实现多对一的关系,是在:多方中创建一个存储一方数据的对象。
具体:每创建一个学生对象,就创建一个老师对象,并且将老师对象存入学生对象中。
在接口中添加一个方法:查找所有学生的信息(包括学生的老师)
实现此方法的重点就在于:每创建一个学生对象,就创建一个老师对象,并且将老师对象存入学生对象中,最后再将学生对象存入学生集合中!!!!
方法实现的源码:(注释很详细,认真看~~)
@Override
public List<Student> getAll() {
{
Connection connection = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
Teacher teacher = null;
try {
//连接数据库
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
String user = "root";
String password = "Wxy180701";
connection = DriverManager.getConnection(url, user, password);
//sql语句
String sql = "select *from student s,teacher t where s.teacherid = t.tid";
//传入sql语句
pps = connection.prepareStatement(sql);
//获得结果集
resultSet = pps.executeQuery();
//创建学生集合,用力啊存储所有学生的信息
List<Student> students = new ArrayList<Student>();
while (resultSet.next()) {
//new学生对象
Student student = new Student();
//存属性
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
//newteacher对象
teacher = new Teacher();
//存teacher的信息
teacher.setTid(resultSet.getInt("tid"));
teacher.setTname(resultSet.getString("tname"));
//将teacher对象存入(上面的那一个)学生对象
student.setTeacher(teacher);
//将学生对象存入集合
students.add(student);
}
return students;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭流
try {
if (connection != null) connection.close();
if (pps != null) pps.close();
if (resultSet != null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
}
双向一对一
先创建两张表:
CREATE TABLE `husband` (
`husid` int(11) NOT NULL AUTO_INCREMENT,
`husname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`husid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `husband` VALUES ('1', '小魏');
CREATE TABLE `wife` (
`wifeid` int(11) NOT NULL AUTO_INCREMENT,
`wifename` varchar(255) DEFAULT NULL,
`hid` int(11) DEFAULT NULL,
PRIMARY KEY (`wifeid`),
UNIQUE KEY `uq_wife_hid` (`hid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `wife` VALUES ('1', '小党', '1');
我们上面学习了一对多,就是在一方中创建多方的集合,多对一就是在多方创建一个一方的对象。那么一对一,想必大家也能猜到,就是在任意一方,创建另一方的对象即可!
建议大家先自己动手试一下,然后与下面的源码对照:
1、丈夫:
package bean;
public class Husband {
private int husid;
private String husname;
public String getHusname() {
return husname;
}
public void setHusname(String husname) {
this.husname = husname;
}
public int getHusid() {
return husid;
}
public void setHusid(int husid) {
this.husid = husid;
}
}
2、妻子:
package bean;
public class Wife {
private int wifeid;
private String wifename;
private int hid;
//存丈夫对象
private Husband husband;
public int getWifeid() {
return wifeid;
}
public void setWifeid(int wifeid) {
this.wifeid = wifeid;
}
public String getWifename() {
return wifename;
}
public void setWifename(String wifename) {
this.wifename = wifename;
}
public int getHid() {
return hid;
}
public void setHid(int hid) {
this.hid = hid;
}
public Husband getHusband() {
return husband;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
}
3、接口:
package Dao;
import bean.Wife;
import java.sql.SQLException;
public interface WifeDao {
//通过输入wife的序号,获得妻子的信息和其丈夫的信息
public Wife findBywife(int wifeid) throws SQLException;
}
4、接口的实现类:
package Dao.Impl;
import Dao.WifeDao;
import bean.Husband;
import bean.Wife;
import java.sql.*;
public class WifeDaoImpl implements WifeDao {
@Override
public Wife findBywife(int wifeid) throws SQLException {
ResultSet resultSet = null;
Connection connection = null;
PreparedStatement pps = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
String userName = "root";
String password = "Wxy180701";
connection = DriverManager.getConnection(url, userName, password);
String sql="SELECT *from wife,husband where wife.hid=husband.husid";
pps = connection.prepareStatement(sql);
resultSet = pps.executeQuery();
//将数据库中的信息存入对象中
while(resultSet.next()){
Wife wife = new Wife();
wife.setWifeid(resultSet.getInt(wifeid));
wife.setWifename(resultSet.getString("wifeName"));
wife.setHid(resultSet.getInt("husid"));
Husband husband = new Husband();
husband.setHusid(resultSet.getInt("husid"));
husband.setHusname(resultSet.getString("husname"));
//将丈夫对象存入妻子中
wife.setHusband(husband);
return wife;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection!=null)connection.close();
if (pps !=null) pps.close();
if (resultSet!=null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
}
5、测试:
package test;
import Dao.Impl.WifeDaoImpl;
import Dao.WifeDao;
import bean.Wife;
import java.sql.SQLException;
public class Demo2 {
public static void main(String[] args) throws SQLException {
WifeDaoImpl wifeDao = new WifeDaoImpl();
Wife wife = wifeDao.findBywife(1);
System.out.println("妻子:"+wife.getWifename()+","
+wife.getWifename()+"的丈夫"
+wife.getHusband().getHusname());
}
}
结果:
多对多
先建立三张表:
学生表:
科目表:
中间表:
在实体类中,找关系:在学生类中需建立学科类集合,在学科类中,也需要学生类集合。
源码:
学生类:
package bean;
import java.util.ArrayList;
import java.util.List;
public class Student2 {
private int stuid;
private String stuname;
private int teacherid;
//在学生类中建立学科类集合
List<Subjects> subjects = new ArrayList<Subjects>();
public int getStuid() {
return stuid;
}
public void setStuid(int stuid) {
this.stuid = stuid;
}
public String getStuname() {
return stuname;
}
public void setStuname(String stuname) {
this.stuname = stuname;
}
public int getTeacherid() {
return teacherid;
}
public void setTeacherid(int teacherid) {
this.teacherid = teacherid;
}
public List<Subjects> getSubjects() {
return subjects;
}
public void setSubjects(List<Subjects> subjects) {
this.subjects = subjects;
}
}
学科类:
package bean;
import java.util.ArrayList;
import java.util.List;
public class Subjects {
private int subid;
private String subname;
//在学科类中添加学生类集合
public List<Student2> getStudent2List() {
return student2List;
}
public void setStudent2List(List<Student2> student2List) {
this.student2List = student2List;
}
private List<Student2> student2List = new ArrayList<Student2>();
public int getSubid() {
return subid;
}
public void setSubid(int subid) {
this.subid = subid;
}
public String getSubname() {
return subname;
}
public void setSubname(String subname) {
this.subname = subname;
}
}
接口:
package Dao;
import bean.Student2;
import bean.Subjects;
public interface SubjectsDao {
//通过学生id查找学生及其所学科目
public Student2 findBystuid(int id);
//通过科目编号查找所有学此科目的学生名字
public Subjects findBysubid(int id);
}
实现接口方法的类:(与上面都大同小异,不同的地方都用注释写了,需要留意!)
package Dao.Impl;
import Dao.SubjectsDao;
import bean.Student2;
import bean.Subjects;
import bean.Teacher;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class SubjectsDaoImpl implements SubjectsDao {
@Override
public Student2 findBystuid(int id) {
{
Connection connection = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
Teacher teacher = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
String user = "root";
String password = "Wxy180701";
connection = DriverManager.getConnection(url, user, password);
String sql = "select *from student s ,`subject` su,middle m where s.stuid=m.stuid and m.subid=su.subid and s.stuid=?";
pps = connection.prepareStatement(sql);
pps.setInt(1,id);
resultSet = pps.executeQuery();
//new一个学生对象在循环外!
Student2 student = new Student2();
//new一个学科集合用来存下面的学科对象
List<Subjects> subjects = new ArrayList<Subjects>();
while (resultSet.next()) {
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
//每循环一次new一个学科
Subjects subject = new Subjects();
subject.setSubid(resultSet.getInt("subid"));
subject.setSubname(resultSet.getString("subname"));
//将学科对象存入学科集合
subjects.add(subject);
}
//将学科集合存入学生对象
student.setSubjects(subjects);
return student;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) connection.close();
if (pps != null) pps.close();
if (resultSet != null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
}
@Override
public Subjects findBysubid(int id) {
{
Connection connection = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
Teacher teacher = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
String user = "root";
String password = "Wxy180701";
connection = DriverManager.getConnection(url, user, password);
String sql = "select *from student s ,`subject` su,middle m where s.stuid=m.stuid and m.subid=su.subid and su.subid=?";
pps = connection.prepareStatement(sql);
pps.setInt(1,id);
resultSet = pps.executeQuery();
//new一个学科对象在循环外!
Subjects subjects = new Subjects();
//new一个学生集合用来存下面的学生对象
List<Student2> student2List = new ArrayList<Student2>();
while (resultSet.next()) {
Student2 student2 = new Student2();
student2.setStuid(resultSet.getInt("stuid"));
student2.setStuname(resultSet.getString("stuname"));
//将student对象存入集合
student2List.add(student2);
subjects.setSubid(resultSet.getInt("subid"));
subjects.setSubname(resultSet.getString("subname"));
}
//将学生集合存入学科对象
subjects.setStudent2List(student2List);
return subjects;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) connection.close();
if (pps != null) pps.close();
if (resultSet != null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
} }
}
多表关系操作总结:
1、当在一张表中建立另一张表的关系时,若对方为“一方”则在此表中创建“一方”的对象作为属性。若对方为“多方”,则创建集合作为属性。
2、在用set方法获取对方表信息时,要注意对方为“多方”时,则set集合属性,集合属性中set“多方”自身表中的“多方”对象,多方对象分别set多方的属性。若对方为“一方”,则是set“一方”的对象,对象set其自身属性!
七、数据库事务
不了解事务的,请先移步下面这篇博文~
事务概述
事务开始于:
1、连接到数据库上,并执行一条DML语句insert、update或delete
2、前一个事务结束后,又输入了另一条DML语句
事务结束于:
1、执行commit或rollback语句。
2、 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
3、执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
4、 断开与数据库的连接
5、执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
JDBC中事务应用
注意:我们一般连接的事务都处于自动提交模式,默认情况下,每个sql语句在完成后都会提交到数据库。那么将“自动提交”切换为“手动提交”,就要使用Connection对象的setAutoCommit()方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
我用代码来演示一下:
我们先获得一张表:
现在我们添加一条数据:(手误:将下面的preparStatement改为createStatement)
结果:表中添加成功!
如果我们将事务调成手动模式:
现在新添加一条数据:小红
代码返回结果:
但实际上,数据库刷新后并未有“小红”:
我们现在关闭事务:(手误:将下面的preparStatement改为createStatement)
数据库中,有小红了!
注意:我们还可以在catch块中加入回滚操作:如果数据添加异常,将回滚此次操作
肯定很多同学都跟我有一样的疑惑:事务明明可以设置成自动,为什么要手动呢?
那么在后面的案例中会给大家讲到,而且在一些复杂的业务逻辑中,也是不得不用手动来提交事务,所以还是要掌握滴~
保存点Savepoints
什么是保存点呢?比如我们在玩通关游戏的时候,今天玩了三关,挂了。那么下次玩游戏时候,就会继续从上次挂了的地方开始,而不是从头再打一遍,这就是因为游戏有保存点。
那么在程序中也一样,如果我们一个事务中有多条语句,假如走着走着,遇到了bug,那么数据将回滚,根据事务的原子性,我们知道,所有在此事务中的数据,一个都添加不进去。那不白添加了!!那么保存点就如同游戏中的保存点一样,在你设置保存点的地方之前,全部都保存下来,遇到bug,只回滚到保存点就行了~
注意:setSavepoint返回对象为savepoint对象,我将其初值赋为null,放在了全局变量的位置。
为了捕捉算数异常,我将异常换成顶级父类 Exception,并且在rollback中添加回滚到的位置----保存点。注意!!回滚后也要提交事务(进行保存)保存回滚点之前的数据!
运行后:有异常。
打开数据表:小红a还是添加进来了~
源码:
import java.sql.*;
public class Demo1 {
public static void main(String[] args) throws SQLException {
Connection connection = null;
Statement statement1 = null;
Statement statement2 = null;
Savepoint savepoint = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String userName = "root";
String password = "Wxy180701";
String url = "jdbc:mysql://localhost:3306/haha?serverTimezone=UTC";
connection = DriverManager.getConnection(url, userName, password);
//事务手动开启:
connection.setAutoCommit(false);
String sql = "insert into emp values (7,'小红a',2)";
String sql2 = "insert into emp values (8,'小红b',2)";
//sql1
statement1 = connection.createStatement();
//sql2
statement2 = connection.createStatement();
//执行sql1
int result1 = statement1.executeUpdate(sql);
//设置保存点,参数为保存点名称
savepoint = connection.setSavepoint("savepoint");
//执行sql2
int result2 = statement2.executeUpdate(sql2);
//bug:
System.out.println(3/0);
//关闭事务:
connection.commit();
if (result1>0) System.out.println("执行成功1");
else System.out.println("执行失败1!");
if (result2>0)System.out.println("执行成功2");
else System.out.println("执行失败2!");
} catch (Exception e) {
e.printStackTrace();
//回滚
connection.rollback(savepoint);
System.out.println("数据回滚");
connection.commit();
} finally {
//关闭流
try {
if (connection!=null) connection.close();
if (statement1!=null) statement1.close();
if (statement2!=null) statement2.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
事务案例
我们上面说到,事务可以自动提交,为什么要设置为手动提交呢?
在这里,我就来给大家演示,事务手动提交的业务需求
转账业务:创建一个数据表
id为1的用户给id为2的用户转一百元
如果遇到上面说的bug(删除保存点),那么数据全部添加失败,数据表中内容不变!
但如果不是手动开启事务:程序遇到bug报错,但数据表已经更新,因为它bug前已经提交了事务。所以一方减去了100,另一方却没有加100,造成数据错乱。
这就是手动开启事务的重要性,可以保证事务全部成功或全部失败。保证了数据的一致性。
源码:
import java.sql.*;
public class Demo2 {
public static void main(String[] args) throws SQLException {
Connection connection = null;
Statement statement1 = null;
Statement statement2 = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String userName = "root";
String password = "Wxy180701";
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
connection = DriverManager.getConnection(url, userName, password);
//事务手动开启:
connection.setAutoCommit(false);
String sql = "update money set money=1000-100 where userid = 1";
String sql2 = "update money set money=1000+100 where userid = 2";
//sql1
statement1 = connection.createStatement();
//sql2
statement2 = connection.createStatement();
//执行sql1
int result1 = statement1.executeUpdate(sql);
//bug:
System.out.println(3/0);
//执行sql2
int result2 = statement2.executeUpdate(sql2);
//关闭事务:
connection.commit();
if (result1>0) System.out.println("执行成功1");
else System.out.println("执行失败1!");
if (result2>0)System.out.println("执行成功2");
else System.out.println("执行失败2!");
} catch (Exception e) {
e.printStackTrace();
//回滚
connection.rollback();
System.out.println("数据回滚");
connection.commit();
} finally {
//关闭流
try {
if (connection!=null) connection.close();
if (statement1!=null) statement1.close();
if (statement2!=null) statement2.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
批处理
批量处理可以将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。
当我们一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。
基于statement实现批处理
现有一个students表
批处理就是用statement对象调用addbatch,将sql语句都添加到批处理中,
最后修改时的方法为executeBatch();
注意:只要是增删改的语句都可以添加到一个批处理中。
结果:
基于preparedstatement的批处理
由于preparedstatement是需要用占位符,所以注意语法即可。
结果:
八、反射处理结果集
我们之前处理得到的结果集,都是通过对象set方法,结果集get方法来一一获取的,当然,如果列名比较少的时候,用这种方法是没有任何问题的,但在实际开发中,列名一般都很多,那么再这样一一获取,就相当繁琐了。于是我们应该用反射来处理获得的结果集!
主要思路:
1、获得数据库中的所有列名
2、通过传入类来反射获得类对象,再用类对象获得类方法
3、用类方法名与列名匹配,如果相同则将列名对应的信息存入方法中。eg:setid与“set”+“id”(列名)相匹配,则存入id属性。
源码:请仔细阅读注释
@Override
public List<Student> getAllStudents(Class cla) {
Connection connection = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
try {
//连接数据库
Class.forName("com.mysql.cj.jdbc.Driver");
String userName = "root";
String password = "Wxy180701";
String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
connection = DriverManager.getConnection(url, userName, password);
String sql = "select *from Student";
pps = connection.prepareStatement(sql);
resultSet = pps.executeQuery();
//学生集合
List students = new ArrayList<>();
//先得到数据库所有的列名信息
//1、得到结果集的信息(对象列的数量,类型和属性)
ResultSetMetaData metaData = resultSet.getMetaData();
//2、得到列数
int columnCount = metaData.getColumnCount();
//获取所有列名(存入一个数组中)
String[] columNames = new String[columnCount];
for(int i=0;i<columnCount;i++) {
//用得到的结果集信息对象获取结果集中的列名(列名是从1开始的)
String columnName = metaData.getColumnName(i + 1);
//将列名存入列名集合
columNames[i] = columnName;
}
//得到类中的所有方法名
//用传入的类的对象调用“忽略私有权限获得所有方法“存入method集合
Method[] declaredMethods = cla.getDeclaredMethods();
//匹配method集合和列名集合
//遍历列名集合
while(resultSet.next()){
//用构造方法创建一个对象
Constructor constructor = cla.getConstructor();
Object stu = constructor.newInstance();
for (String columName : columNames) {
//装饰列名,set列名即为方法名
String methodName = "set"+columName;
//遍历方法名,找与列名相同的
for (Method declaredMethod: declaredMethods) {
//忽略大小写比较
if (declaredMethod.getName().equalsIgnoreCase(methodName)){
//将结果集得到的列名赋给stu对象
declaredMethod.invoke(stu,resultSet.getObject(columName));
//存储后break这一层
break;
}
}
}
//存入对象中
students.add(stu);
}
return students;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
try {
if(connection!=null) connection.close();
if (pps!=null) pps.close();
if (resultSet!=null) resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
测试:传入参数:类类型为Studnet类
结果:
九、工具类的定义
写了上面那么多代码,其实我们会发现,一开始的连接数据库操作一直在重复,造成代码量很大,那我们是否可以将其封装起来,调用它即可。答案是:当然可以。
封装工具类:
在src下创建一个新的包,util,util下再创建类DBUtil。
package util;
import java.sql.*;
import java.util.List;
public class DBUtils {
//1、定义变量
private Connection connection;
private PreparedStatement pps;
private ResultSet resultSet;
private int count;//记录受影响的行数
private String userName = "root";
private String passWord = "Wxy180701";
private String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
//2、加载驱动
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3、获得连接
protected Connection getConnection() {
try {
connection = DriverManager.getConnection(url, userName, passWord);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//4、获得预状态通道
protected PreparedStatement getPps(String sql) {
try {
pps = getConnection().prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return pps;
}
//5、绑定参数
protected void param(List list){ //传入参数集合
if (list != null) {
for(int i=0;i<list.size();i++){
try {
pps.setObject(i+1,list.get(i));
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//6、执行操作(增删改+查询)
protected int update(String sql,List list){
getPps(sql);
param(list);
try {
count = pps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}
//7、查询
protected ResultSet query(String sql,List list){
getPps(sql);
param(list);
ResultSet resultSet = null;
try {
resultSet = pps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return resultSet;
}
//8、关闭资源
protected void closeAll(){
try {
if (connection != null) {
connection.close();
}
if (pps != null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
操作一波:
注意:实现接口的类也要继承刚写的工具类DButil,这样DButil中写的保护类和私有属性才能访问到!!
方法:建议对照上面写的DBUtil类来看~~
public Student findById(int id) {
//定义sql语句
String sql = "select *from student where stuid=?";
//占位符的集合
List list = new ArrayList();
list.add(id);
//query查询结果集方法返回结果集
ResultSet rs = query(sql, list);
Student student = new Student();
try {
while (rs.next()) {
//给stu对象赋值
student.setStuid(rs.getInt("stuid"));
student.setStuname(rs.getString("stuname"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭资源
closeAll();
}
System.out.println();
return student;
}
这次的方法是不是简洁了很多!!
测试结果:
十、属性文件
我们上面写的DBUtil类中,在连接数据库的操作位置还有四个固定的字符串,但其实如果有后期修改的话,还是不是很方便的。
所以我们在开发过程中是需要创建一个属性文件来进行动态的数据绑定的!
在src下创建,文件名叫啥都无所谓,但是后缀必须是properties!
在属性文件中存储四个信息:驱动地址,用户名,密码,链接地址。(等号前的key值是自定义的)。
DBUtil的加载驱动替换为下文代码:
//2、加载驱动
static {
try {
//resourcebundle类用来读取资源属性文件,传入属性文件名
ResourceBundle bundle = ResourceBundle.getBundle("db");
url = bundle.getString("url");
DriverName = bundle.getString("driverclass");
userName = bundle.getString("uname");
passWord = bundle.getString("pw");
Class.forName(DriverName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注意:以前写的属性在static中要用,就要写成静态属性!
运行结果:和以前一样~
十一、连接池
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
最小连接数:
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量数据库资源被浪费。
初始化连接数:
连接池启动时创建的初始化数据库连接数量。
最大连接数:
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到待队列中。
最大等待时间:
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0者负数使得无限等待(根据不同连接池配置)。
三种连接池对应的属性:
连接池实现原理:每次用时从头取出一个连接,用完的连接会添加到末尾从而保持池中数量不变!
public class Pool{
static LinkedList<Connection> list = new LinkedList<Connection>();
static{
for (int i = 0; i < 10; i++) {
Connection connection = JDBCUtils.newInstance().getConnection();
list.add(connection);
}
}
public static Connection getConnection(){
if (list.isEmpty()) {
//JDBCUtils类是自定义类,封装了连接数据库的信息代码
Connection connection = JDBCUtils.newInstance().getConnection();
list.addLast(connection);
}
Connection conn = list.removeFirst();
return conn;
}
public static void addBack(Connection conn){
if (list.size() >= 10) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
list.addLast(conn); //10
}
}
public static int getSize(){
return list.size();
}
}
DBCP连接池(了解)
需导入jar包(大家可以在网上搜索也可以私聊我问我要,博主目前还不会分享在这里~)
1、先读取属性文件
2、用工具类完成数据库连接与加载驱动(创建的工具类在全局变量方便后续使用)
注意:后续的获得连接也要用工具类来获得连接哦~
源码:
static {
//DBCP
//1、读取属性文件
ResourceBundle bundle = ResourceBundle.getBundle("db");
url = bundle.getString("url");
DriverName = bundle.getString("driverclass");
userName = bundle.getString("uname");
passWord = bundle.getString("pw");
//用工具类来完成数据库连接与加载驱动
basicDataSource.setUsername(userName);
basicDataSource.setPassword(passWord);
basicDataSource.setUrl(url);
basicDataSource.setDriverClassName(DriverName);
//添加一些初始的操作
//eg:初始最小连接数为20
basicDataSource.setInitialSize(20);
}
C3P0连接池(了解)
c3p0与dbcp区别
1.
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能
2.
dbcp需要手动设置配置文件(调用set方法设置信息)
c3p0不需要手动设置
一、 用C3P0时需要加载一个配置文件(在src下):默认名:c3p0-config.xml(文件名固定)
在文件中,复制如下xml:(注意:必须要根据自己的改driverClass、url、user、password这几项,下面的最大连接数、空闲时间等都根据自己需求去配置)
<?xml version="1.0" encoding="utf-8"?>
<c3p0-config>
<!-- 默认配置,如果没有指定则使用这个配置 -->
<default-config>
<!-- 基本配置 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">Wxy180701</property>
<!--扩展配置-->
<!-- 连接超过30秒报错-->
<property name="checkoutTimeout">30000</property>
<!--30秒检查空闲连接 -->
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">10</property>
<!-- 30秒不适用丢弃-->
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
</c3p0-config>
二、导入jar包(大家可以在网上搜索也可以私聊我问我要,博主目前还不会分享在这里~)
三、
获得连接:
即可。所以我们可以发现,代码中就只是创建了个对象,剩下的所有信息都是在配置文件中加载出来的!
德鲁伊连接池(用的最多)
了解资料:
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个
ProxyDriver(代理驱动),一系列内置的JDBC组件库,一个SQL Parser(sql解析器)。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQLParser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter就是通过Druid的SQL Parser分析语义实现的。
Druid 是目前比较流行的高性能的,分布式列存储的OLAP框架(具体来说是MOLAP)。
它有如下个特点:
一. 亚秒级查询
druid提供了快速的聚合能力以及亚秒级的OLAP查询能力,多租户的设计,是面向用户分析应用的理想方式。
二.实时数据注入
druid支持流数据的注入,并提供了数据的事件驱动,保证在实时和离线环境下事件的实效性和统一性。
三.可扩展的PB级存储
druid集群可以很方便的扩容到PB的数据量,每秒百 万级别的数据注入。即便在加大数据规模的情况下,也能保证时其效性。
四.多环境部署
druid既可以运行在商业的硬件上,也可以运行在云上。它可以从多种数据系统中注入数据,包括
hadoop,spark,kafka,storm和samza等
五.丰富的社区
druid拥有丰富的社区,供大家学习
使用步骤:
一、添加jar包
二、创建对象并且读取属性文件,加载驱动 (也可自定义设置连接池相关属性,最大空闲时间等)
三、获得连接
测试结果:
那么到这里, JDBC就全部写完啦~ 欢迎大家收藏 & 你的点赞是我的动力~~