Java学习——Java JDBC(下)
文章目录
一、对数据库表的操作
两表关系,有四种:双向一对一,一对多,多对一,多对多
多表关系处理数据
(1). 数据库通过外键建立两表关系
(2). 实体类通过属性的方式建立两表关系
实体类要求:类名=表名,列名=属性名
1. 多对多关系的处理
数据表:
middle表:
student表:
subject表:
我们先在bean包下建立两个实体类(创建实体类,用于将数据库里的内容取出存入对象,加载至内存中):
Student类:
import java.util.List;
public class Student {
private int stuid;
private String stuname;
private int teacherid;
private Teacher teacher;
private List<Subject> subjects; //创建一个Subject类型的集合存储subject对象,以实现多对多的关系:多个学生对应着多门学科
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 Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public List<Subject> getSubjects() {
return subjects;
}
public void setSubjects(List<Subject> subjects) {
this.subjects = subjects;
}
}
Subject类:
import java.util.List;
public class Subject {
private int subid;
private String subname;
private List stulist; //创建一个list存储student对象,以实现多对多的关系:多门学科对应着多个学生的选修
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;
}
public List getStulist() {
return stulist;
}
public void setStulist(List stulist) {
this.stulist = stulist;
}
}
我们先在dao包下创建一个接口,简单定义需要实现的方法:
import test.bean.Student;
import test.bean.Subject;
public interface SubjectDao {
//查询某个学生的所有学科信息
public Student findById(int id);
//查询某个学科的所选的学生信息
public Subject findBySubId(int subId);
}
接着创建一个实现类,实现接口:
SubjectDaoImpl.java,类中包含了对数据库的操作方法
import test.bean.Student;
import test.bean.Subject;
import test.dao.SubjectDao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class SubjectDaoImpl implements SubjectDao {
//通过学生id查找学生学科信息
@Override
public Student findById(int id) {
//操作数据库
Connection conn = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String username = "root";
String password = "111111";
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url, username, password);
//定义sql
String sql = "SELECT * from student stu,subject sub,middle m WHERE stu.stuid=m.stuid and m.subid=sub.subid and stu.stuid=?";
pps = conn.prepareStatement(sql);
pps.setInt(1, id); //参数设置需要放到上面那条语句之后
//执行sql
resultSet = pps.executeQuery();
Student student = new Student();
List<Subject> subjectList = new ArrayList<>();
while (resultSet.next()) {
//取出各自信息
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
Subject subject = new Subject();
subject.setSubid(resultSet.getInt("subid"));
subject.setSubname(resultSet.getString("subname"));
subjectList.add(subject);
}
//建立学生与学科的关系
student.setSubjects(subjectList);
return student;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
@Override
public Subject findBySubId(int subId) {
//操作数据库
Connection conn = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String username = "root";
String password = "111111";
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url, username, password);
//定义sql
String sql = "SELECT * from student stu,subject sub,middle m WHERE stu.stuid=m.stuid and m.subid=sub.subid and sub.subid=?";
pps = conn.prepareStatement(sql);
pps.setInt(1, subId);
//执行sql
resultSet = pps.executeQuery();
Subject subject = new Subject();
List<Student> studentList = new ArrayList();
while (resultSet.next()) {
//取出各自信息
Student student = new Student();
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
studentList.add(student);
subject.setSubid(resultSet.getInt("subid"));
subject.setSubname(resultSet.getString("subname"));
}
//建立学生与学科的关系
subject.setStulist(studentList);
return subject;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
我们在Demo中测试这个类:
import test.bean.Student;
import test.bean.Subject;
import test.dao.impl.SubjectDaoImpl;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
SubjectDaoImpl subjectDao = new SubjectDaoImpl();
Student student = subjectDao.findById(1);
System.out.println("学生姓名:" + student.getStuname());
List<Subject> subjects = student.getSubjects();
for (Subject subject : subjects) {
System.out.println("所学科目:" + subject.getSubname() + "\t科目id:" + subject.getSubid());
}
System.out.println("----------------------分割线------------------------");
Subject subject = subjectDao.findBySubId(2);
System.out.println("科目:" + subject.getSubname() + "\t科目id:" + subject.getSubid());
List<Student> stulist = subject.getStulist();
for (Student student1 : stulist) {
System.out.println("选课学生:" + student1.getStuname() + "\t学生id:" + student1.getStuid());
}
}
}
测试结果:
二、数据库事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。
事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
简单来说:
一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。
事务开始于
- 连接到数据库上,并执行一条DML语句insert、update或delete
- 前一个事务结束后,又输入了另一条DML语句
事务结束于
- 执行commit或rollback语句。
- 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
- 执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
- 断开与数据库的连接
- 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语
句。
1. 事务四大特点
(ACID)
- Atomicity (原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败 - consistency (一致性)
表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态 - isolation (隔离性)
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。 - durability (持久性)
持久性事务完成之后,它对于系统的影响是永久性的。
(1). JDBC中事务的应用
如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。
事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,使用Connection对象的setAutoCommit()方法。
如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
(2). 事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用commit()方法,如下所示:
- conn.commit( );
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码
- conn.rollback( );
例:
try {//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees values (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees VALUES (107, 22, 'Sita', 'Singh')";
stmt.executeUpdate(SQL); // If there is no error.
conn.commit();
} catch (SQLException se) { // If there is any error.
conn.rollback();
}
2. Savepoints
新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点
- setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
- releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint
对象作为参数。此对象通常是由setSavepoint()方法生成的保存点
例:
try {//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
String SQL = "INSERTED IN Employees VALUES (107, 22, 'Sita', 'Tez')";
stmt.executeUpdate(SQL);
conn.commit();
} catch (SQLException se) {
conn.rollback(savepoint1);
}
示例:
简单创建一个money表:
在Demo3中实现事务的所有相关操作:
import java.sql.*;
/**
* 数据事务流程的实现
*/
public class Demo3 {
public static void main(String[] args) {
//操作数据库
Connection conn = null;
Statement statement = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
Savepoint a = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String username = "root";
String password = "111111";
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url, username, password);
conn.setAutoCommit(false); //将事务设置为手动提交
//定义sql
statement = conn.createStatement();
//执行sql
//事务的特性示例:
int result1 = statement.executeUpdate("update money set salary = salary-1000 where name='张三'");
int result2 = statement.executeUpdate("update money set salary = salary-1000 where name='李四'");
/*
* 手动设置异常
* 体现事务的原子性,事务内的执行情况均为失败 ,所有操作都会被撤销,不会出现其中某些操作已被执行的情况
* */
System.out.println(5/0);
conn.commit(); //事务提交
/*
//事务的保存点与回滚操作的示例:
statement.executeUpdate("insert into money values('王五',3000)");
//设置事务保存点
a = conn.setSavepoint("a");
statement.executeUpdate("insert into money values('老六',2000)");
System.out.println(5/0); //手动设置异常
conn.commit();
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
//出现SQL异常便会到回滚到保存点a
try {
conn.rollback(a);
conn.commit(); //将回滚保存点之前的sql操作进行提交
System.out.println("出现异常,回滚成功!");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
首先我们运行的代码逻辑为:事务的特性示例那一段:
我们查看数据库,可以看到事务中的所有操作由于异常的出现并未执行成功。
我们将事务的特性示例那一段代码注释掉,进行事务的保存点与回滚操作的示例
查看数据库,可以发现即使出现了异常,但是在catch块中执行力回滚操作,回到了上一个保存点 a。并成功执行了a点保存点之前的所有操作:
三、JDBC的批处理
批量处理允许您将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。
当您一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。
分 Statement状态批处理 与 PrepareStatement预状态批处理
1. Statement批处理
以下是使用语句对象的批处理的典型步骤序列
- 使用createStatement()方法创建Statement对象。
- 使用setAutoCommit()将auto-commit设置为false 。
- 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
- 最后,使用commit()方法提交所有更改。
2. PreparedStatement批处理
- 使用占位符创建SQL语句。
- 使用prepareStatement() 方法创建PrepareStatement对象。
- 使用setAutoCommit()将auto-commit设置为false 。
- 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
- 最后,使用commit()方法提交所有更改。
批处理示例:
money表:
import java.sql.*;
/**
* 批处理的实现(包含状态与预状态通道)
*/
public class Demo4 {
public static void main(String[] args) {
//操作数据库
Connection conn = null;
Statement statement = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
Savepoint a = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String username = "root";
String password = "111111";
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url, username, password);
conn.setAutoCommit(false); //将事务设置为手动提交
/**
* 使用状态通道实现批处理
*/
//定义sql,创建状态通道
statement = conn.createStatement();
//执行sql
String sql1 = "insert into money(name) values('a')";
String sql2 = "insert into money(name) values('b')";
String sql3 = "insert into money(name) values('c')";
String sql4 = "insert into money(name) values('d')";
//调用addBatch()方法添加待处理的语句
statement.addBatch(sql1);
statement.addBatch(sql2);
statement.addBatch(sql3);
statement.addBatch(sql4);
//执行上面添加的所有sql语句,并返回执行情况的int类型的结果集
int[] ints = statement.executeBatch();
conn.commit(); //事务的提交
System.out.println("statement状态通道实现批处理,sql语句处理情况");
//对结果集进行遍历
for (int i : ints) {
System.out.println("anInt:" + i);
}
/**
* 使用预状态通道实现批处理
*/
//定义sql,创建预状态通道
pps = conn.prepareStatement("insert into money(name) values(?)");
pps.setString(1,"aa1");
//调用addBatch()方法添加待处理的语句
pps.addBatch();
pps.setString(1,"bb1");
pps.addBatch();
pps.setString(1,"cc1");
pps.addBatch();
pps.setString(1,"dd1");
pps.addBatch();
//执行上面添加的所有sql语句,并返回执行情况的int类型的结果集
int[] ints1 = pps.executeBatch();
conn.commit(); //事务的提交
System.out.println("prepareStatement预状态通道实现批处理,sql语句处理情况");
for (int i : ints1) {
System.out.println("anInt:" + i);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
运行结果:
数据库情况:
四、反射处理结果集与工具类的定义
1. 反射处理结果集
在bean包下定义的实体类:
Teacher.java:
public class Teacher {
private int teacherid;
private String name;
public int getTeacherid() {
return teacherid;
}
public void setTeacherid(int teacherid) {
this.teacherid = teacherid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Studennt.java
import java.util.List;
public class Student {
private int stuid;
private String stuname;
private int teacherid;
private Teacher teacher;
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 Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public List<Subject> getSubjects() {
return subjects;
}
public void setSubjects(List<Subject> subjects) {
this.subjects = subjects;
}
}
在dao包下新建一个接口简单定义一下所需方法:
import test.bean.Student;
import test.bean.Teacher;
import java.util.List;
public interface TeacherDao {
//定义操作方法
//定义一个根据老师id查询老师管理的所有学生
public Teacher getById();
//查询所有的学生信息(包含老师信息)
public List<Student> getAll();
//查询所有老师
public List<Student> getAllStudents(Class cl);
//更根据学生id查询学生信息
public Student getByStuid(int id);
}
编写具体方法实现具体类:
TeacherDaoImpl .java
import test.bean.Student;
import test.bean.Teacher;
import test.dao.TeacherDao;
import test.util.DBUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class TeacherDaoImpl implements TeacherDao {
@Override
public Teacher getById() {
return null;
}
@Override
public List<Student> getAll() {
return null;
}
/**
* 反射处理结果集
* @param cl
* @return
*/
@Override
public List<Student> getAllStudents(Class cl) {
//操作数据库
Connection conn = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获得连接
String username = "root";
String password = "111111";
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url, username, password);
//定义sql
String sql = "SELECT * from student";
pps = conn.prepareStatement(sql);
//执行sql
resultSet = pps.executeQuery();
List studentList = new ArrayList();
//1.得到查询数据库的结果集的列信息
ResultSetMetaData metaData = resultSet.getMetaData(); //存储结果集信息
int columnCount = metaData.getColumnCount(); //得到列数
String[] columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
columnNames[i] = metaData.getColumnName(i+1); //列从i+1开始计算
System.out.println("columnName=" + columnNames[i]);
}
//2.得到类中的所有方法
Method[] methods = cl.getDeclaredMethods();
while (resultSet.next()) {
//(1).取出各自信息
try {
Object o = cl.newInstance();
for (String columnName : columnNames) {
String methodName = "set" + columnName;
for (Method method : methods) {
if (method.getName().equalsIgnoreCase(methodName)) {
method.invoke(o, resultSet.getObject(columnName));
break;
}
}
}
studentList.add(o);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return studentList;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
@Override
public Student getByStuid(int id) {
return ull;
}
}
测试类:
import test.bean.Student;
import test.dao.impl.TeacherDaoImpl;
import java.util.List;
public class Demo6 {
public static void main(String[] args) {
TeacherDaoImpl teacherDao = new TeacherDaoImpl();
List<Student> allStudents = teacherDao.getAllStudents(Student.class);
for (Student student : allStudents) {
System.out.println(student.getStuid() + ", " + student.getStuname() + ", " + student.getTeacherid());
}
}
}
运行结果:
2. 工具类的定义
我们重复了许多数据库的连接操作,所以将它抽成一个工具类:
在src目录下新建一个db.properties文件,用于存储数据库连接的条件:
db.properties,该配置文件要求严谨,属性与值之间关系:保存数据库信息-特点:key-value存储方式,所以不能因为美观之类的有空格什么的。
driverclass=com.mysql.cj.jdbc.Driver
uname=root
upass=111111
url=jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC
新建一个util包,在包中创建一个DBUtils 工具类,工具类中读取属性文件有两个方式不强求使用哪一个:
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;
/**
* 定义数据库工具类
*/
public class DBUtils {
//1.定义变量
private Connection conn;
private PreparedStatement pps;
private ResultSet resultSet;
private int count; //存储数据库操作受影响的行数
private static String username;
private static String password;
private static String url;
private static String driverName;
//2.加载数据库驱动(静态匿名内部类,仅用于加载一次驱动)
static {
try {
//工具类中获取属性文件.properties
// 方法一:使用Properties访问本地资源,可能出现IO异常
InputStream inputStream = DBUtils.class.getClassLoader().getResourceAsStream("db.properties"); //读取文件,转换为输入流
Properties properties = new Properties();
properties.load(inputStream); //加载输入流到properties中
//提取数据,进行赋值
driverName = properties.getProperty("driverclass");
username = properties.getProperty("uname");
password = properties.getProperty("upass");
url = properties.getProperty("url");
// 方法二:使用ResourceBundle访问本地资源,没有IO异常
/*
ResourceBundle bundle = ResourceBundle.getBundle("db");//传入属性文件名称即可
driverName = bundle.getString("driverclass");
username = bundle.getString("uname");
password = bundle.getString("upass");
url = bundle.getString("url");
*/
Class.forName(driverName); //加载驱动
} catch (ClassNotFoundException e ) {
e.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
//3.获得数据库的连接
protected Connection getConn() {
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//4.获取预状态通道
protected PreparedStatement getPps(String sql) {
try {
pps = getConn().prepareStatement(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return pps;
}
//5.绑定参数,传递的list的参数是保存的给sql语句中占位符赋值的内容
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 throwables) {
throwables.printStackTrace();
}
}
}
}
//6.执行SQL操作(增删改 + 查询),返回数据库影响行数
protected int update(String sql,List list) {
pps = getPps(sql);
param(list);
try {
count = pps.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return count;
}
//7.查询操作
protected ResultSet query(String sql,List list) {
getPps(sql);
param(list);
try {
resultSet = pps.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return resultSet;
}
//8.关闭资源
protected void closeAll() {
try {
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
我们在上面的TeacherDaoImpl.java 中的重写的getByStuid方法中实现工具类的使用:
@Override
public Student getByStuid(int id) {}
当然 TeacherDaoImpl需要继承DBUtils工具类:
public class TeacherDaoImpl extends DBUtils implements TeacherDao
/**
* 继承数据库操作工具类后实现的方法
* 简便,并且代码量少
* @param id
* @return
*/
@Override
public Student getByStuid(int id) {
Student student = new Student();
try {
String sql = "select * from student where stuid=?";
List list = new ArrayList();
list.add(id);
ResultSet resultSet = query(sql,list);
while (resultSet.next()) {
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
closeAll();
}
return student;
}
我们继续在Demo6中继续编写 工具类的测试:
import test.bean.Student;
import test.dao.impl.TeacherDaoImpl;
import java.util.List;
public class Demo6 {
public static void main(String[] args) {
TeacherDaoImpl teacherDao = new TeacherDaoImpl();
/* List allStudents = teacherDao.getAllStudents(Student.class);
for (Student student : allStudents) {
System.out.println(student.getStuid() + ", " + student.getStuname() + ", " + student.getTeacherid());
}*/
Student student = teacherDao.getByStuid(1);
System.out.println(student.getStuname() + " ," + student.getStuid());
}
}
运行情况:
五、连接池
数据连接池原理:
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。
使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。
同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
zhu
1. 自定义连接池
我们可以通过自定义的方式实现连接池!分析连接池类应该包含特定的属性和方法!
属性:
集合 放置Connection
方法:
获取连接方法 回收连接方法
最小连接数:
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:
连接池启动时创建的初始化数据库连接数量。
最大连接数:
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
注意:
在DBCP连接池的配置中,还有一个maxIdle的属性,表示最大空闲连接数,超过的空闲连接将被释放,默认值为8。对应的该属性在Druid连接池已不再使用,配置了也没有效果,c3p0连接池则没有对应的属性。
数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从池中取出一个连接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个连接,如果这个等待时间超过了maxWait,则会报错;
如果当前正在使用的连接数没有达到maxActive,则判断当前是否空闲连接,如果有则直接使用空闲连接,如果没有则新建立一个连接。在连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。
2. DBCP连接池
DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。
导入相应jar包
- mysql-jdbc.jar
- commons-dbcp.jar
- commons-pool.jar
硬编码使用DBCP
所谓的硬编码方式就是在代码中添加配置
在原DBUtils修改代码(静态代码块与数据库获得连接处有所改动)
软编码使用DBCP
所谓的软编码,就是在项目中添加配置文件,这样就不需要每次代码中添加配合!
项目中添加配置
文件名称: info.properties
文件位置: src下
#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC
username=root
password=111
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=6000
DButils工具类 代码实现
//1.创建dbcp的工具类对象
static BasicDataSource datasource = new BasicDataSource();
//2.加载驱动
static {
try {
//加载属性文件
// 1.使用工具类 ,参数是属性文件的文件名(不要加后缀)
ResourceBundle bundle = ResourceBundle.getBundle("db");
driverClass = bundle.getString("driverclass");
url = bundle.getString("url");
username = bundle.getString("uname");
password = bundle.getString("upass");
init = bundle.getString("initsize");
//2.将驱动地址等信息传递给dbcp
datasource.setDriverClassName(driverClass);
datasource.setUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setInitialSize(Integer.parseInt(init));
} catch (Exception e) { // TODO Auto-generated catch block
e.printStackTrace();
}
}
//3.获得连接
public static Connection getConn () {
try {
con = datasource.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return con;
}
3. C3P0连接池
c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
c3p0与dbcp区别
(1)、
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能
(2)、
dbcp需要手动设置配置文件
c3p0不需要手动设置
实现方式
- 手动设置 ComboPooledDataSource
- 加载配置文件方式
src/c3p0-config.xml(文件名固定)
ComboPooledDataSource cpds = new ComboPooledDataSource();
加载 文件中 <default-config>中的配置
ComboPooledDataSource cpds = new ComboPooledDataSource("aaa");
加载 <named-config name="aaa"> 中的配置
-
实现步骤
导入jar包c3p0-0.9.1.2.jar
mysql-connector-java-5.0.8.jar
添加配置文件
c3p0是在外部添加配置文件,工具直接进行应用,因为直接引用,所以要求固定的命名和文件位置
文件位置: src
文件命名:c3p0-config.xml/c3p0-config.properties
<?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/mydatabase?serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">111111</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>
4. Druid(德鲁伊)连接池
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个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包
druid-1.0.9.jar , druid-1.0.9-sources(源码包,根据需求)
编写工具类
注:在Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(数据库类型),然后选择相应的driverClassName。
三个连接池的工具类编写:
import com.alibaba.druid.pool.DruidDataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbcp.BasicDataSource;
import java.sql.*;
import java.util.List;
import java.util.ResourceBundle;
/**
* 定义数据库工具类
*/
public class DBUtils2 {
//1.定义变量
private Connection conn;
private PreparedStatement pps;
private ResultSet resultSet;
private int count; //存储数据库操作受影响的行数
private static BasicDataSource basicDataSource = new BasicDataSource(); //DBCP
// private ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //C3P0
private static DruidDataSource druidDataSource = new DruidDataSource(); //Druid
private static String username;
private static String password;
private static String url;
private static String driverName;
//2.加载数据库驱动(静态匿名内部类,仅用于加载一次驱动)
static {
//DBCP
/* ResourceBundle bundle = ResourceBundle.getBundle("db");//传入属性文件名称即可
driverName = bundle.getString("driverclass");
username = bundle.getString("uname");
password = bundle.getString("upass");
url = bundle.getString("url");
basicDataSource.setUsername(username);
basicDataSource.setPassword(password);
basicDataSource.setUrl(url);
basicDataSource.setDriverClassName(driverName);
//连接池属性设置,也可以使用配置文件保存数据,便于整体修改,自定义选择设置
basicDataSource.setInitialSize(20); //初始化连接数
basicDataSource.setMinIdle(5); //最小连接数
basicDataSource.setMaxIdle(20); //最大空闲连接数
basicDataSource.setMaxWait(0); //最大等待时间,一般设置为0或-1,表示无限等待(毫秒)
*/
//Druid
ResourceBundle bundle = ResourceBundle.getBundle("db");//传入属性文件名称即可
driverName = bundle.getString("driverclass");
username = bundle.getString("uname");
password = bundle.getString("upass");
url = bundle.getString("url");
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverName);
//连接池属性设置,也可以使用配置文件保存数据,便于整体修改,自定义选择设置
druidDataSource.setInitialSize(5);
}
//3.获得数据库的连接
protected Connection getConn() {
try {
// conn = basicDataSource.getConnection(); //DBCP
// conn = comboPooledDataSource.getConnection(); //C3P0
conn = druidDataSource.getConnection(); //Druid
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//4.获取预状态通道
protected PreparedStatement getPps(String sql) {
try {
pps = getConn().prepareStatement(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return pps;
}
//5.绑定参数,传递的list的参数是保存的给sql语句中占位符赋值的内容
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 throwables) {
throwables.printStackTrace();
}
}
}
}
//6.执行SQL操作(增删改 + 查询),返回数据库影响行数
protected int update(String sql, List list) {
pps = getPps(sql);
param(list);
try {
count = pps.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return count;
}
//7.查询操作
protected ResultSet query(String sql, List list) {
getPps(sql);
param(list);
try {
resultSet = pps.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return resultSet;
}
//8.关闭资源
protected void closeAll() {
try {
if (conn != null) {
conn.close();
}
if (pps != null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}