1.JDBC
[JAVA]-JDBC
[TOC]
1.JDBC基础
<1>JDBC基础概念
持久化
持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成。
持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中
JAVA中的数据存储技术
在Java中,数据库存取技术可分为如下几类:
JDBC直接访问数据库
JDO技术
第三方O/R工具,如Hibernate, ibatis(MyBatis) 等
JDBC是java访问数据库的基石,JDO, Hibernate等只是更好的封装了JDBC
JDBC的作用
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法、方便地访问数据库资源
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程
JDBC接口(API)包括两个层次:
面向应用的API:
JDBC API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
面向数据库的API:
JDBC Driver API,供开发商开发数据库驱动程序用
<2>JDBC中的API
JDBC相关对象
JDBC API 是一系列的接口,它使得应用程序能够进行数据库联接,执行SQL语句,并且得到返回结果
类 | 说明 |
---|---|
java.sql.DriverManager | 用来装载驱动程序,获取数据库连接 |
java.sql.Connection | 完成对某一指定数据库的联接 |
java.sql.Statement | 在一个给定的连接中作为SQL执行声明的容器,他包含了两个重要的子类型 |
java.sql.PreparedSatement | 用于执行预编译的sql声明 |
java.sql.CallableStatement | 用于执行数据库中存储过程的调用 |
java.sql.ResultSet | 对于给定声明取得结果的途径 |
1>DriverManager
DriverManager:驱动管理对象
1)加载和注册JDBC驱动
管理一组 JDBC 驱动程序的基本服务
加载 JDBC 驱动需调用 Class 类的静态方法 forName( ),向其传递要加载的 JDBC 驱动的类名
DriverManager 类是驱动程序管理器类,负责管理驱动程序
通常不用显式调用 DriverManager 类的 registerDriver( ) 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver( ) 方法来注册自身的一个实例
2)建立连接
可以调用 DriverManager 类的 getConnection( ) 方法建立到数据库的连接
在建立连接的时候需要四个参数连接数据库 :用户名、密码、url、driver
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接
JDBC URL的标准由三部分组成,各部分间用冒号分隔
jdbc:<子协议>:<子名称>
协议:JDBC -- URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息
对于 MySql数据库连接:
jdbc:mysql://localhost:3306/test
对于 SQLServer 数据库连接:
jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=sid
对于 MYSQL 数据库连接:
jdbc:mysql://localhost:3306/sid
如果是远程连接数据库,则将相应的localhost换成相应的ip地址
2>Connection
Connection:数据库连接对象
1)获取执行者对象
- 获取普通执行者对象:
Statement createStatement();
- 获取预编译执行者对象:
PreparedStatement prepareStatement(String sql);
2)管理事务
- 开启事务:
setAutoCommit(boolean autoCommit);
参数为false,则开启事务。 - 提交事务:
commit();
- 回滚事务:
rollback();
3)释放资源
- 立即将数据库连接对象释放:
void close();
3>Statement
Statement:执行sql语句的对象
通过调用 Connection 对象的 createStatement 方法创建该对象,该对象用于执行静态的 SQL 语句,并且返回执行结果。Statement 接口中定义了下列方法用于执行SQL语句或资源释放
# 执行DML语句
int excuteUpdate(String sql) # 可执行insert、update、delete语句,返回影响的行数
# 执行DQL语句
ResultSet excuteQuery(String sql) # 可执行select语句,返回ResultSet封装查询的结果
# 资源释放
void close() # 将执行者对象释放
4>ResultSet
ResultSet:结果集对象
通过Statement 对象的 excuteQuery() 查询到的结果集是ResultSet就是一张数据表,ResultSet对象以逻辑表格的形式封装了执行数据库操作之后的结果集,ResultSet接口由数据库厂商实现。ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的next( )方法移动到下一行
ResultSet 接口的常用方法:
# 判断结果集中是否还有数据
boolean next() # 有数据返回true,并将索引向下移动一行;没有数据返回false
# 获取结果集中的数据
XXX getXxx("列名") # XXX表示数据类型,例如getString()、getInt()等
# 资源释放
void close() # 释放结果集对象
分析:初始状态指向第一条记录的前面,执行next()方法,若返回true就向下移动一行
5>PrepareStatement
可以通过调用 Connection
对象的 preparedStatement( )
方法获取 PreparedStatement
对象
PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXXX( ) 方法来设置这些参数. setXXX( ) 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
数据库的连接
1>连接说明
开启数据库的相关服务:
OracleServiceORCL
、OracleDb11g_home1TNSListener
加载jar包到指定工程:
在oracle数据库的安装目录下的指定路径查找到相应的jar包
本机查找路径:D:\Oracle_Databse\product\11.2.0\dbhome_1\jdbc\lib
选择ojdbc6.jar文件,复制到指定的工程下新建的lib文件夹中
右键点击objbc6文件,选择“Build Path”-->“Add to Build Path”,添加完成后可看到工程新增了以下的内容
编写代码完成数据库连接测试
四个属性的设定:用户名、密码分别为登录oracle数据库的用户名、密码
驱动查找:ojdbc6.jar包下的“oracle.jdbc.driver”包下的“OracleDriver.class”
驱动driver=”oracle.jdbc.driver.OracleDriver ”;
连接url=”jdbc:oracle:thin:@localhost:1521:orcl ”;
通过 Class.forName(driver);
获取驱动
通过DriverManager的getConnection方法获取数据库连接:
DriverManager.getConnection(url, username, password)
返回获取的数据库连接
2>普通连接
public class JDBCUtils {
/**
* 1.定义数据库连接的四个属性
* 用户名 username:haha
* 密码 password:haha
* 驱动 driver:
* 连接 url:
*/
private static String username = "haha";
private static String password = "haha";
private static String dirver = "oracle.jdbc.driver.OracleDriver";
private static String url = "jdbc:oracle:thin:@localhost:1521:orcl";
/**
* 2.获取数据库连接
* a.加载驱动:Class.forName(driver);
* b.通过DriverManager的getConnection方法获取数据库连接
* c.最终返回获取的连接
* 为了方便说明此处处理异常统一抛给上一级处理
* @throws Exception
*/
public static Connection getConnection() throws Exception
{
Connection conn ;
//a.加载驱动
Class.forName(dirver);
//b.通过DriverManager的getConnection方法获取数据库连接
conn = DriverManager.getConnection(url, username, password);
//c.最终返回获取的连接
return conn;
}
public static void main(String[] args) {
//测试与数据库的连接
try {
System.out.println(JDBCUtils.getConnection());
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:如果测试成功,会相应返回一个内存地址,如果连接失败则会提示相应的连接错误提示
3>属性配置
在src文件夹下存放属性配置文件“jdbc.properties”,里面相应包含数据库连接所需要的四个属性
public class JDBCFileUtils {
/**
* 通过配置文件的设置,使得连接数据库的代码具有通用性
* 1.加载配置文件
* 2.获取配置文件的相关属性:用户名、密码、驱动、url
* 3.获取数据库的连接
* a.加载驱动:Class.forName(driver);
* b.通过DriverManager的getConnection方法获取数据库连接
* c.最终返回获取的连接
* 为了方便说明此处处理异常统一抛给上一级处理
* @throws Exception
*/
public static Connection getConnection() throws Exception
{
/**
* 加载配置文件
* a.创建Properties对象
* b.利用当前类的类加载器完成配置文件的加载
* 类名.class.getClassLoader().getResourceAsStream(文件名)
* 返回值为IuputStream类型
* 如果没有明确要查找的文件的路径,则默认是在src文件夹下进行查找,
* 因此如果连接失败的情况下要尝试着分析配置文件的放置路径是否正确
* JDBCFileUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
* c.获取配置文件的相关属性
*/
Properties p = new Properties();
//1.利用当前类的类加载器完成配置文件的加载
InputStream in =JDBCFileUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
p.load(in);
//2.获取配置文件中的相关属性信息
String username = p.getProperty("jdbc.username");
String password = p.getProperty("jdbc.password");
String driver = p.getProperty("jdbc.driver");
String url = p.getProperty("jdbc.url");
Connection conn = null;
//3.创建连接:加载驱动、建立连接、返回创建的连接
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
return conn;
}
public static void main(String[] args) {
//测试与数据库的连接
try {
System.out.println(JDBCFileUtils.getConnection());
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:如果测试成功,会相应返回一个内存地址,如果连接失败则会提示相应的连接错误提示
4>单例模式(双重验证)
/**
* 将数据库连接设置为单例模式(一次只能在内存中存在一个对象)
* 1.定义私有的静态的属于自己的变量
* 2.构造函数私有化
* 3.通过静态代码块初始化相关的属性(数据库连接的四个属性)
* 4.单例获取JDBCUtils工具类 : 双重验证模式(区分懒汉式、饿汉式)
* a.判断当前对象是否为空
* b.如果为空则进行锁定synchronized (JDBCUtils.class) {...}
* c.二次判断当前对象是否为空
* (有可能存在在锁定的时候其他的对象已经创建了,则不需要重复创建)
* d.返回当前的对象
* 5.获取数据库连接:创建连接、加载驱动
* 最终数据库连接获取方式:JDBCUtils.getConnection();
*/
public class JDBCUtils {
//1.定义私有的静态的属于自己的变量
private static JDBCUtils jdbcutils;
/**
* 定义数据库连接的四个属性
* 用户名 username:haha
* 密码 password:haha
* 驱动 driver:
* 连接 url:
*/
private static String username = null;
private static String password = null;
private static String driver = null;
private static String url = null;
//2.构造函数私有化
private JDBCUtils()
{
}
//3.通过静态代码块初始化相关的属性
static
{
Properties p = new Properties();
try {
// 利用当前类的类加载器完成配置文件的加载
InputStream in = JDBCUtils.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
p.load(in);
} catch (IOException e) {
e.printStackTrace();
}
// 获取配置文件中的相关属性信息
username = p.getProperty("jdbc.username");
password = p.getProperty("jdbc.password");
driver = p.getProperty("jdbc.driver");
url = p.getProperty("jdbc.url");
}
/**
* 获取数据库连接
* a.加载驱动:Class.forName(driver);
* b.通过DriverManager的getConnection方法获取数据库连接
* c.最终返回获取的连接
* 为了方便说明此处处理异常统一抛给上一级处理
* @throws Exception
*/
//4.单例获取JDBCUtils工具类 : 双重验证模式
public static JDBCUtils getJDBCUtils()
{
//判断当前jdbcutils是否为空
if(jdbcutils==null)
{
//如果jdbcutils为空则进行锁定
synchronized (JDBCUtils.class) {
/**
* 再进行二次判断,因为有可能会出现加锁的同时另外一个对象
* 已经创建了新的内容,此时则不需要再次创建
*/
if(jdbcutils==null)
{
jdbcutils=new JDBCUtils();
}
}
}
//由双重锁验证获取当前的对象
return jdbcutils;
}
//5.获取数据库连接
public static Connection getConnection()
{
Connection conn = null;
// 创建连接:加载驱动、建立连接、返回创建的连接
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
//关闭连接
public static void freeAll(Connection conn,Statement st,ResultSet rs)
{
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
if(st!=null)
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
if(rs!=null)
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5>优化
考虑到之后的测试中始终需要对相应的文件、连接进行操作,为了保证操作的安全性,每次操作结束之后均要关闭相应的连接,因此为了避免重复代码的冗余,考虑将关闭连接的操作封装为一个方法
freeAll(Connection conn,Statement st,ResultSet rs)
//关闭连接
public static void freeAll(Connection conn,Statement st,ResultSet rs)
{
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
if(st!=null)
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
if(rs!=null)
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
<3>CRUD
普通方式操作
public class CRUD1 {
/**
* 普通更新的方式实现数据的增删改查
* 基本步骤:
* a.编写sql语句
* 需要确保编写的sql语句能够在数据库中正确执行
* b.获取数据库连接
* Connection conn = JDBCUtils.getConnection();
* Connection conn = JDBCFileUtils.getConnection();
* c.通过conn的createStatementz()方法获取Statement对象
* Statement st = conn.createStatementz();
* d.执行相应的sql语句
* 新增表格、添加数据、修改数据、删除数据直接执行executeUpdate方法
* st.executeUpdate(sql);
* 查询数据需要通过ResultSet对象接收返回的结果集
* ResultSet rs = st.executeQuery(sql);
* e.关闭打开的连接 通过JDBCUtils或JDBCFileUtils的freeAll方法实现
* 按照以上步骤一步步实现即可,但与此同时也需要注意加以区分
* 为了方便叙述,此处处理异常全部向上一级抛出
* @throws Exception
*/
// 1.创建表格
public static void createTbale() throws Exception {
/**
* 创建学生表stu
* sid number(10) primary key
* sname varchar2(20) not null
* gender vahchar2(4) check
* descr varchar2(30) unique
*/
// a.编写正确的sql语句
String sql = "create table stu(" + "sid number(10) primary key,"
+ "sname varchar2(20) not null,"
+ "gender varchar2(4) check(gender='男' or gender='女'),"
+ "descr varchar2(30) unique)";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处为执行executeUpdate方法
st.executeUpdate(sql);
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
// 2.增加数据
public static void insert() throws Exception {
// a.编写正确的sql语句
String sql = "insert into stu values(1,'张三','男','我是张三')";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处为执行executeUpdate方法
st.executeUpdate(sql);
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
// 3.删除数据
public static void delete() throws Exception {
// a.编写正确的sql语句
String sql = "delete from stu where sid=1";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处为执行executeUpdate方法
st.executeUpdate(sql);
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
// 4.修改数据
public static void update() throws Exception {
// a.编写正确的sql语句
String sql = "update stu set descr='我是修改后的内容哦' where sid=2";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处为执行executeUpdate方法
st.executeUpdate(sql);
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
// 5.查询数据
public static void query() throws Exception {
// a.编写正确的sql语句
String sql = "select * from stu";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处需要通过ResultSet对象接收查询返回的结果
ResultSet rs = st.executeQuery(sql);
while(rs.next())
{
//不同的方式实现打印获取的数据
/**
* 方式1:
* 用指定的列名获取数据,但需要明确数据的类型
* rs.getInt(列名);
* rs.getDouble(列名);
* rs.getString(列名);
* ......
*/
System.out.print("方式1:"+rs.getInt("sid")+"-"
+rs.getString("sname")+"-"
+rs.getString("gender")+"-"
+rs.getString("descr")+"\n");
/**
* 方式2:
* 用指定的序号获取数据,但需要明确数据的类型
* 序号的顺序与表中的列定义顺序相对应,从1开始
* rs.getInt(1);
* rs.getDouble(2);
* rs.getString(3);
* ......
*/
System.out.print("方式2:"+rs.getInt(1)+"-"
+rs.getString(2)+"-"
+rs.getString(3)+"-"
+rs.getString(4)+"\n");
/**
* 方式3:
* 如果并不明确指定列对应的数据类型,则可通过getObject方法获取
* rs.getObject("sid");
* rs.getObject(2);
* ......
*/
System.out.print("方式3:"+rs.getObject(1)+"-"
+rs.getObject(2)+"-"
+rs.getObject(3)+"-"
+rs.getObject(4)+"\n");
}
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, rs);
}
public static void main(String[] args) {
try {
// 测试增删改查,在数据库中检查
// CRUD1.createTbale();
// CRUD1.insert();
// CRUD1.delete();
// CRUD1.update();
// CRUD1.query();
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果分析:可结合数据库进行测试,查看结果的正确性
- 创建表stu
- 新增数据(多次修改sql语句、执行)
- 删除数据
- 修改数据
- 查询所有数据
缺点:代码虽然简单、形式单一,但却没有很好的通用性(将sql语句写死!),不能很好地实现需求,可以作为简单参考,在此基础上应该更好地完善,下述通过绑定变量方式更新进行叙述
绑定变量方式操作
public class CRUD2 {
/**
* 绑定变量更新的方式实现数据的增删改查
* 基本步骤:
* 1.创建实际操作对象类,其属性与数据库内容一一对应,将数据封装到实体对象中
* 2.通过基本绑定变量的方式实现数据的增删改查,实现代码的通用性
* a.编写sql语句
* 需要确保编写的sql语句能够在数据库中正确执行
* eg:String sql = "insert into stu values(?,?,?,?,?)";
* 用问号?指代要传入的参数,在之后传入指定的参数
* b.获取数据库连接
* Connection conn = JDBCUtils.getConnection();
* Connection conn = JDBCFileUtils.getConnection();
* c.通过conn的createStatementz()方法获取Statement对象
* Statement st = conn.createStatementz();
* 或是通过conn的prepareStatement()方法预编译执行语句
* PreparedStatement ps = conn.prepareStatement(sql);
* d.执行相应的sql语句
* 新增表格执行executeUpdate方法
* st.executeUpdate(sql);
* 添加数据、修改数据、删除数据
* 按照指定的数据类型将相应的参数传入,后执行execute方法
* ps.setInt(1, 参数1);
* ps.setString(2, 参数2);
* ..........
* ps.setInt(n, ...);
* ps.execute();
* 在前面的语句中已经执行预编译,此处则直接进行编译执行execute方法即可
* 查询数据需要通过ResultSet对象接收返回的结果集
* ResultSet rs = st.executeQuery();
* 在前面的语句中已经执行预编译,此处则直接进行编译执行executeQuery方法即可
* e.关闭打开的连接 通过JDBCUtils或JDBCFileUtils的freeAll方法实现
* 按照以上步骤一步步实现即可,但与此同时也需要注意加以区分
* 为了方便叙述,此处处理异常全部向上一级抛出
*/
// 1.创建表格
public static void createTbale() throws Exception {
/**
* 创建学生表stu
* sid number(10) primary key
* sname varchar2(20) not null
* gender vahchar2(4) check
* descr varchar2(30) unique
*/
// a.编写正确的sql语句
String sql = "create table stu(" + "sid number(10) primary key,"
+ "sname varchar2(20) not null,"
+ "gender varchar2(4) check(gender='男' or gender='女'),"
+ "descr varchar2(30) unique)";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过Statement对象st执行sql语句,此处为执行executeUpdate方法
st.executeUpdate(sql);
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
//2.增加数据
public static void insert(Student stu) throws Exception {
// a.编写正确的sql语句
String sql = "insert into stu values(?,?,?,?)";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql语句
PreparedStatement st = conn.prepareStatement(sql);
// d.传入相应的参数,并执行execute方法
st.setInt(1, stu.getSid());
st.setString(2, stu.getSname());
st.setString(3, stu.getGender());
st.setString(4, stu.getDescr());
st.execute();
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
//3.删除数据:假设此处根据sid删除学生信息
public static void delete(int sid) throws Exception {
// a.编写正确的sql语句
String sql = "delete from stu where sid = ?";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql语句
PreparedStatement st = conn.prepareStatement(sql);
// d.传入相应的参数,并执行execute方法
st.setInt(1, sid);
st.execute();
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
//4.修改数据:假设此处根据sid修改学生信息
public static void update(Student stu,int sid) throws Exception {
// a.编写正确的sql语句
String sql = "update stu set sid=?,sname=?,gender=?,descr=? where sid=?";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql语句
PreparedStatement st = conn.prepareStatement(sql);
// d.传入相应的参数,并执行execute方法
st.setInt(1, stu.getSid());
st.setString(2, stu.getSname());
st.setString(3, stu.getGender());
st.setString(4, stu.getDescr());
st.setInt(5, sid);
st.execute();
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, null);
}
//5.查询数据:查询指定sid的学生信息
public static Student queryOne(int sid) throws Exception {
Student newStu = new Student();
// a.编写正确的sql语句
String sql = "select * from stu where sid=?";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql语句
PreparedStatement st = conn.prepareStatement(sql);
// d.传入相应的参数,并执行executeQuery方法,通过ResultSet对象接收查找的信息
st.setInt(1, sid);
ResultSet rs = st.executeQuery();
while(rs.next())
{
//封装查询的内容,返回对象
newStu.setSid(rs.getInt(1));
newStu.setSname(rs.getString(2));
newStu.setGender(rs.getString(3));
newStu.setDescr(rs.getString(4));
return newStu;
}
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, rs);
//查找失败返回为空
return null;
}
//5.查询数据:返回当前表中所有学生信息
public static List<Student> queryAll() throws Exception {
List<Student> list = new ArrayList<Student>();
// a.编写正确的sql语句
String sql = "select * from stu";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
// d.传入相应的参数,并执行executeQuery方法,通过ResultSet对象接收信息
ResultSet rs = st.executeQuery();
while(rs.next())
{
//封装查询的内容,将其装载到列表中
Student newStu = new Student(rs.getInt(1),rs.getString(2),rs.getString(3),rs.getString(4));
list.add(newStu);
}
// e.关闭打开的连接
JDBCUtils.freeAll(conn, st, rs);
return list;
}
public static void main(String[] args) throws Exception {
//测试绑定变量方式更新数据的增删改查
//1.创建表格
// CRUD2.createTbale();
//2.添加以下数据
// Student s1 = new Student(1,"张三","男","我是张三");
// Student s2 = new Student(2,"李四","男","我是李四");
// Student s3 = new Student(3,"王五","女","我是王五");
// CRUD2.insert(s1);
// CRUD2.insert(s2);
// CRUD2.insert(s3);
//3.删除sid为2的学生信息
// CRUD2.delete(2);
//4.修改sid为1的学生信息:修改相应desc为“我是修改后的内容啊啊啊”
// Student newStu = new Student(1,"张三","男","我是修改后的内容啊啊啊");
// CRUD2.update(newStu, 1);
// //5.查询sid为1的学生信息
// System.out.println("查找到的学生信息为:"+CRUD2.queryOne(1));
//6.查询所有学生信息
// List<Student> list = CRUD2.queryAll();
// for(Student s : list)
// System.out.println(s);
}
}
结果分析:可结合数据库进行测试,查看结果的正确性
- 创建表stu
- 新增数据
- 删除数据
- 修改数据
- 查询单一数据
- 查询所有数据
案例分析
1>设计分析
创建表格,插入指定的数据,设计输入显示实现数据的录入、删除、修改、信息的查询
字段名 | 说明 | 类型 |
---|---|---|
flow_id | 流水号 | int |
type | 四级/六级 | int |
id_card | 身份证号码 | varchar(18) |
exam_card | 准考证号码 | varchar(15) |
student_name | 学生姓名 | varchar(20) |
location | 区域 | varchar(20) |
grade | 成绩 | int |
结合题目需求,在前面所学案例的基础上可以进一步对代码继续进行优化
设计相应的界面提示,将获取用户输入部分独立出来
根据用户的输入决定要执行的sql语句,再将String类型的sql传入相应的方法中
考虑重复代码的问题,尝试着将部分重复代码进行整合,整合为一个通用的方法。此处可以考虑将insert、delete、update方法整合成为一个通用的方法,通过传入的参数决定要执行的操作(根据不同的需求传入sql语句,以及所需的参数)
可以直接根据内容拼接sql语句,也可通过预编译sql语句,将相关参数传入后执行
另外独立封装增删改查方法进行测试
2>代码分析
public class Person {
private int flowId;//流水号
private int type;//四级/六级
private String idCard;//身份证号码
private String examCard;//准考证号码
private String studentName;//学生姓名
private String location;//区域
private int grade;//成绩
public Person() {
}
public Person(int flowId, int type, String idCard, String examCard,
String studentName, String location, int grade) {
super();
this.flowId = flowId;
this.type = type;
this.idCard = idCard;
this.examCard = examCard;
this.studentName = studentName;
this.location = location;
this.grade = grade;
}
public int getFlowId() {
return flowId;
}
public void setFlowId(int flowId) {
this.flowId = flowId;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getExamCard() {
return examCard;
}
public void setExamCard(String examCard) {
this.examCard = examCard;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
@Override
public String toString() {
return "Person [flowId=" + flowId + ", type=" + type + ", idCard="
+ idCard + ", examCard=" + examCard + ", studentName="
+ studentName + ", location=" + location + ", grade=" + grade
+ "]";
}
}
普通方式CRUD
PersonCRUD.java
public class PersonCRUD {
//1.创建表格
public static void createTable() throws Exception
{
//a.编译sql语句
String sql = "create table person("
+ "flowId number(10),type number(10),idCard varchar2(18),"
+ "examCard varchar2(15),studentName varchar2(20),"
+ "location varchar2(20),grade number(3))";
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
//d.通过executeUpdate方法执行sql语句
st.executeUpdate(sql);
//e.关闭连接
JDBCUtils.freeAll(conn, st, null);
}
//2.增加数据
public static void insert(Person p) throws Exception
{
//a.编译sql语句
String sql = "insert into person values(?,?,?,?,?,?,?)";
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
//d.插入指定的数据,通过execute方法执行sql语句
st.setInt(1, p.getFlowId());
st.setInt(2, p.getType());
st.setString(3, p.getIdCard());
st.setString(4, p.getExamCard());
st.setString(5, p.getStudentName());
st.setString(6, p.getLocation());
st.setInt(7, p.getGrade());
st.execute();
//e.关闭连接
JDBCUtils.freeAll(conn, st, null);
}
//3.修改学生数据:通过流水号进行查找修改
public static void update(Person newPerson,int flowId) throws Exception
{
//a.编译sql语句
String sql = "update person set flowId=?,type=?,idCard=?,examCard=?,"
+ "studentName=?,location=?,grade=? where flowId=?";
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
//d.插入指定的数据,通过execute方法执行sql语句
st.setInt(1, newPerson.getFlowId());
st.setInt(2, newPerson.getType());
st.setString(3, newPerson.getIdCard());
st.setString(4, newPerson.getExamCard());
st.setString(5, newPerson.getStudentName());
st.setString(6, newPerson.getLocation());
st.setInt(7, newPerson.getGrade());
st.setInt(8, flowId);
st.execute();
//e.关闭连接
JDBCUtils.freeAll(conn, st, null);
}
//4.删除学生数据:根据学生准考证删除、根据学生身份证号码删除
public static void delete(String sql) throws Exception
{
//a.编写sql语句,sql语句通过判断操作的方式进行获取,由以参数传递的方式实现
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
//d.插入指定的数据,通过execute方法执行sql语句
st.execute();
//e.关闭连接
JDBCUtils.freeAll(conn, st, null);
}
//5.查询学生信息:以学生准考证号查询、以学生身份证号查询
public static Person queryOne(String sql) throws Exception
{
//a.编写sql语句,sql语句通过判断操作的方式进行获取,由以参数传递的方式实现
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
//d.通过executeQuery方法执行sql语句,并用ResultSet对象接收信息
ResultSet rs = st.executeQuery();
while(rs.next())
{
Person p = new Person(rs.getInt(1),rs.getInt(2),rs.getString(3),rs.getString(4),
rs.getString(5),rs.getString(6),rs.getInt(7));
return p;
}
//e.关闭连接
JDBCUtils.freeAll(conn, st, rs);
//查找失败返回null
return null;
}
//6.得到所有学生信息
public static List<Person> queryAll() throws Exception
{
List<Person> list = new ArrayList<Person>();
//a.编写sql语句
String sql = "select * from person";
//b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
//d.通过executeQuery方法执行sql语句,并用ResultSet对象接收信息
ResultSet rs = st.executeQuery();
while(rs.next())
{
Person p = new Person(rs.getInt(1),rs.getInt(2),rs.getString(3),rs.getString(4),
rs.getString(5),rs.getString(6),rs.getInt(7));
//将数据加载到list列表中
list.add(p);
}
//e.关闭连接
JDBCUtils.freeAll(conn, st, rs);
//返回查找的结果
return list;
}
}
Menu.java
public class Menu {
//主菜单
public static void show()
{
System.out.println("请选择要进行的操作:");
System.out.println("1.创建表格");
System.out.println("2.插入数据");
System.out.println("3.删除数据");
System.out.println("4.修改数据");
System.out.println("5.查询单个数据");
System.out.println("6.查询所有数据");
System.out.println("7.退出操作");
}
//选择菜单
public static void showChoice()
{
System.out.println("请选择以何种方式进行操作");
System.out.println("1.根据学生准考证号进行操作");
System.out.println("2.根据学生身份证号进行操作");
}
//获取用户的选择菜单的选择输入
public static int getInputChoice()
{
Scanner sc = new Scanner(System.in);
int c = sc.nextInt();
if(c!=1 && c!=2)
{
System.out.println("用户输入操作有误,请重新输入!");
getInputChoice();
}
return c;
}
//获取用户的主菜单的选择输入
public static int getInput()
{
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
if(i<1||i>7)
{
System.out.println("用户输入操作有误,请重新输入!");
getInput();
}
return i;
}
//需要通过获取不同的用户选择,拼接相应的sql语句,从而实现功能的测试
public static String getDeleteSql()
{
showChoice();
Scanner sc = new Scanner(System.in);
String sql = "delete from person";
int choice = getInputChoice();
if(choice==1)
{
System.out.println("请输入要删除的学生的准考证号");
String examCard = sc.next();
sql += " where examCard = "+examCard;
}
else if(choice==2)
{
System.out.println("请输入要删除的学生的身份证号");
String idCard = sc.next();
sql += " where idCard = "+idCard;
}
return sql;
}
public static String getQuerySql()
{
showChoice();
Scanner sc = new Scanner(System.in);
String sql = "select * from person";
int choice = getInputChoice();
if(choice==1)
{
System.out.println("请输入要查询的学生的准考证号");
String examCard = sc.next();
sql += " where examCard = "+examCard;
}
else if(choice==2)
{
System.out.println("请输入查询的学生的身份证号");
String idCard = sc.next();
sql += " where idCard = "+idCard;
}
return sql;
}
//插入数据
public static Person insertData()
{
Scanner sc = new Scanner(System.in);
System.out.println("请根据提示录入相应的学生信息");
System.out.print("FlowId:");
int flowId = sc.nextInt();
System.out.print("\nType:");
int type = sc.nextInt();
System.out.print("\nidCard:");
String idCard = sc.next();
System.out.print("\nexamCard:");
String examCard = sc.next();
System.out.print("\nstudentName:");
String studentName = sc.next();
System.out.print("\nlocation:");
String location = sc.next();
System.out.print("\ngrade:");
int grade = sc.nextInt();
Person p = new Person(flowId,type,idCard,examCard,studentName,location,grade);
return p;
}
//根据用户的不同输入,测试相应的功能
public static void test() throws Exception
{
show();
Scanner sc = new Scanner(System.in);
switch(getInput())
{
case 1 :
{
PersonCRUD.createTable();
break;
}
case 2:
{
PersonCRUD.insert(insertData());
break;
}
case 3:
{
PersonCRUD.delete(getDeleteSql());
break;
}
case 4:
{
System.out.print("请输入要修改的学生流水号:");
int flowId = sc.nextInt();
Person p = insertData();
PersonCRUD.update(p, flowId);
break;
}
case 5:
{
Person findPerson = PersonCRUD.queryOne(getQuerySql());
if(findPerson!=null)
System.out.println(findPerson);
else
System.out.println("抱歉,您所要查找的学生信息不存在,请重新进行操作!");
break;
}
case 6:
{
List<Person> list = PersonCRUD.queryAll();
for(Person p:list)
System.out.println(p);
break;
}
case 7:
{
System.out.println("测试结束,自动退出......");
System.exit(0);
}
}
}
public static void main(String[] args) {
try {
while(true)
{
test();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
优化版本
PersonCRUD2.java
public class PersonCRUD2 {
// 1.创建表格
public static void createTable() throws Exception {
// a.编译sql语句
String sql = "create table person("
+ "flowId number(10),type number(10),idCard varchar2(18),"
+ "examCard varchar2(15),studentName varchar2(20),"
+ "location varchar2(20),grade number(3))";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的createStatement方法获取Statement对象
Statement st = conn.createStatement();
// d.通过executeUpdate方法执行sql语句
st.executeUpdate(sql);
// e.关闭连接
JDBCUtils.freeAll(conn, st, null);
}
// 2.增加数据、删除数据、修改数据
// 整合通用的方法:insert、delete、update
public static void update(String sql, Object... args) throws Exception {
// a.sql语句由根据相应的内容进行设置
// b.获取数据库链接
Connection conn = JDBCUtils.getConnection();
// c.通过conn获取prepareStatement对象,预编译sql语句
PreparedStatement ps = conn.prepareStatement(sql);
// d.将相关数据插入,并执行executeUpdate方法
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
// e.关闭打开的连接
JDBCUtils.freeAll(conn, ps, null);
}
// 3.查询学生信息:以学生准考证号查询、以学生身份证号查询
public static Person queryOne(String sql) throws Exception {
// a.编写sql语句,sql语句通过判断操作的方式进行获取,由以参数传递的方式实现
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
// d.通过executeQuery方法执行sql语句,并用ResultSet对象接收信息
ResultSet rs = st.executeQuery();
while (rs.next()) {
Person p = new Person(rs.getInt(1), rs.getInt(2), rs.getString(3),
rs.getString(4), rs.getString(5), rs.getString(6),
rs.getInt(7));
return p;
}
// e.关闭连接
JDBCUtils.freeAll(conn, st, rs);
// 查找失败返回null
return null;
}
// 4.得到所有学生信息
public static List<Person> queryAll() throws Exception {
List<Person> list = new ArrayList<Person>();
// a.编写sql语句
String sql = "select * from person";
// b.获取数据库连接
Connection conn = JDBCUtils.getConnection();
// c.通过conn的prepareStatement方法获取PreparedStatement对象,预编译sql
PreparedStatement st = conn.prepareStatement(sql);
// d.通过executeQuery方法执行sql语句,并用ResultSet对象接收信息
ResultSet rs = st.executeQuery();
while (rs.next()) {
Person p = new Person(rs.getInt(1), rs.getInt(2), rs.getString(3),
rs.getString(4), rs.getString(5), rs.getString(6),
rs.getInt(7));
// 将数据加载到list列表中
list.add(p);
}
// e.关闭连接
JDBCUtils.freeAll(conn, st, rs);
// 返回查找的结果
return list;
}
}
Menu2.java
public class Menu2 {
//主菜单
public static void show()
{
System.out.println("请选择要进行的操作:");
System.out.println("1.创建表格");
System.out.println("2.插入数据");
System.out.println("3.删除数据");
System.out.println("4.修改数据");
System.out.println("5.查询单个数据");
System.out.println("6.查询所有数据");
System.out.println("7.退出操作");
}
//选择菜单
public static void showChoice()
{
System.out.println("请选择以何种方式进行操作");
System.out.println("1.根据学生准考证号进行操作");
System.out.println("2.根据学生身份证号进行操作");
}
//获取用户的选择菜单的选择输入
public static int getInputChoice()
{
Scanner sc = new Scanner(System.in);
int c = sc.nextInt();
if(c!=1 && c!=2)
{
System.out.println("用户输入操作有误,请重新输入!");
getInputChoice();
}
return c;
}
//获取用户的主菜单的选择输入
public static int getInput()
{
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
if(i<1||i>7)
{
System.out.println("用户输入操作有误,请重新输入!");
getInput();
}
return i;
}
//用户根据提示插入数据
public static Person insertData()
{
Scanner sc = new Scanner(System.in);
System.out.println("请根据提示录入相应的学生信息");
System.out.print("FlowId:");
int flowId = sc.nextInt();
System.out.print("\nType:");
int type = sc.nextInt();
System.out.print("\nidCard:");
String idCard = sc.next();
System.out.print("\nexamCard:");
String examCard = sc.next();
System.out.print("\nstudentName:");
String studentName = sc.next();
System.out.print("\nlocation:");
String location = sc.next();
System.out.print("\ngrade:");
int grade = sc.nextInt();
Person p = new Person(flowId,type,idCard,
examCard,studentName,location,grade);
return p;
}
//a.创建表格
public static void create()
{
try {
PersonCRUD.createTable();
} catch (Exception e) {
e.printStackTrace();
}
}
//b.增加数据
public static void addPerson()
{
String sql = "insert into person values(?,?,?,?,?,?,?)";
Person p = insertData();
try {
PersonCRUD2.update(sql, p.getFlowId(),p.getType(),p.getIdCard(),
p.getExamCard(),p.getStudentName(),p.getLocation(),p.getGrade());
} catch (Exception e) {
e.printStackTrace();
}
}
//c.修改数据
public static void updatePerson()
{
Scanner sc = new Scanner(System.in);
String sql = "update person set flowId=?,type=?,idCard=?,examCard=?,"
+ "studentName=?,location=?,grade=? where flowId=?";
System.out.print("请输入要修改的学生流水号:");
int flowId = sc.nextInt();
Person p = insertData();
try { PersonCRUD2.update(sql,p.getFlowId(),p.getType(),p.getIdCard(),p.getExamCard(),
p.getStudentName(),p.getLocation(),p.getGrade(),flowId);
} catch (Exception e) {
e.printStackTrace();
}
}
//d.删除数据:通过准考证号码或身份证号码进行删除
public static void deletePerson()
{
//需要通过获取不同的用户选择,拼接相应的sql语句,从而实现功能的测试
showChoice();
Scanner sc = new Scanner(System.in);
String sql = "delete from person";
int choice = getInputChoice();
if(choice==1)
{
System.out.println("请输入要删除的学生的准考证号");
String examCard = sc.next();
sql += " where examCard = "+examCard;
}
else if(choice==2)
{
System.out.println("请输入要删除的学生的身份证号");
String idCard = sc.next();
sql += " where idCard = "+idCard;
}
try {
PersonCRUD2.update(sql);
} catch (Exception e) {
e.printStackTrace();
}
}
//e.查找单个数据:通过准考证号码或身份证号码进行查找
public static Person QueryPerson()
{
showChoice();
Scanner sc = new Scanner(System.in);
String sql = "select * from person";
int choice = getInputChoice();
if(choice==1)
{
System.out.println("请输入要查询的学生的准考证号");
String examCard = sc.next();
sql += " where examCard = "+examCard;
}
else if(choice==2)
{
System.out.println("请输入查询的学生的身份证号");
String idCard = sc.next();
sql += " where idCard = "+idCard;
}
try {
return PersonCRUD2.queryOne(sql);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//f.查找所有数据
public static void QueryAllPerson()
{
List<Person> list;
try {
list = PersonCRUD2.queryAll();
for(Person p : list)
System.out.println(p);
} catch (Exception e) {
e.printStackTrace();
}
}
//根据用户的不同输入,测试相应的功能
public static void test()
{
show();
Scanner sc = new Scanner(System.in);
switch(getInput())
{
case 1 :
{
Menu2.create();
break;
}
case 2:
{
Menu2.addPerson();
break;
}
case 3:
{
Menu2.deletePerson();
break;
}
case 4:
{
Menu2.updatePerson();
break;
}
case 5:
{
Person findPerson = Menu2.QueryPerson();
if(findPerson!=null)
System.out.println("查找到的学生信息如下:"+findPerson);
else
System.out.println("抱歉,您所要查找的学生信息不存在,请重新进行操作!");
break;
}
case 6:
{
Menu2.QueryAllPerson();
break;
}
case 7:
{
System.out.println("测试结束,自动退出......");
System.exit(0);
}
}
}
public static void main(String[] args) {
//手动插入数据
Person[] p = {
new Person(1,4,"412824195263214584","200523164754000","张锋","郑州",85),
new Person(2,4,"222224195263214584","200523164754001","孙朋","大连",56),
new Person(3,6,"342824195263214584","200523164754002","刘明","沈阳",72),
new Person(4,6,"100824195263214584","200523164754003","赵虎","哈尔滨",95),
new Person(5,4,"454524195263214584","200523164754004","杨丽","北京",64),
new Person(6,4,"854624195563214584","200523164754005","王小红","太原",60)};
for(int i=0;i<p.length;i++)
{
String sql = "insert into person values(?,?,?,?,?,?,?)";
try {
PersonCRUD2.update(sql, p[i].getFlowId(),p[i].getType(),p[i].getIdCard(),
p[i].getExamCard(),p[i].getStudentName(),p[i].getLocation(),p[i].getGrade());
} catch (Exception e) {
e.printStackTrace();
}
}
while(true)
{
test();
}
}
}
<4>PrepareStatement VS Statement
从代码的可读性和可维护性、安全性等方面阐述
- PreparedStatement 能最大可能提高性能
DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义。事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次.
(语法检查,语义检查,翻译成二进制命令,缓存)
- PreparedStatement 可以防止 SQL 注入
SQL注入攻击
在数据库中创建相应的表格进行测试
public class SqlInjectionTest {
public static void main(String[] args) throws Throwable {
/**
* 输入错误的用户名、密码进行测试
*/
String username = "a ' or password= ";
String password = " or '1'='1";
//非法测试
illegalLogin(username, password);//通过(错误)
//合法测试
normalLogin(username, password);//失败(正确)
}
/**
* sql注入攻击问题
* 用户登录问题:
* 用户输入错误的用户名或密码也能够登录成功
* @throws Exception
*/
public static void illegalLogin(String username, String password)
throws Exception {
/**
* 问题分析:
* 使用Statement对象执行sql语句 sql语句是通过拼接随后再执行的,
* 因此数据库可能会辨识出不同的内容,从而导致二义性,即便是不存在的
* 用户名或密码也能够成功通过 检测,得到错误的信息
*/
// 编写sql语句
String sql = "select * from users where username='" + username
+ "' and password='" + password + "'";
System.out.println(sql);
// 获取数据库连接
Connection conn = JDBCUtils.getConnection();
// 创建Statement对象并执行sql语句
Statement st = conn.createStatement();
// 创建ResultSet接受结果集
ResultSet rs = st.executeQuery(sql);
// 判断结果是否存在
if (rs.next()) {
System.out.println(rs.getInt(1)+rs.getString(2)+rs.getString(3));
System.out.println("登录成功...");
} else {
System.out.println("登录失败...");
}
//关闭连接
JDBCUtils.freeAll(conn, st, rs);
}
public static void normalLogin(String username, String password) throws Exception {
/**
* 问题解决:
* 使用PreparedStatement进行优化 PreparedStatement会对sql语句进行预编译
* 因此传入的username、password是直接作为整个内容传入,
* 而非通过拼接进行操作,因此不会具有二义性
*/
// 编写sql语句
String sql = "select * from users where username=? and password=?";
// 获取数据库连接
Connection conn = JDBCUtils.getConnection();
// 创建Statement对象并执行sql语句,传入相应的参数
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
// 创建ResultSet接受结果集
ResultSet rs = ps.executeQuery();
// 判断结果是否存在
if(rs.next()) {
System.out.println("登录成功...");
} else {
System.out.println("登录失败...");
}
//关闭连接
JDBCUtils.freeAll(conn, ps, rs);
}
}
PreparStatement 提高效率
一次性插入太多数据可能会报相应的错误,因此在创建连接的时候要注意连接的使用量、最大连接数
ORA-12519, TNS:no appropriate service handler found
public class BatchSqlTest {
/**
* 批量sql操作处理:
* 情景:向数据库中插入10万条数据(可以操作更多,时间、效率对比更加明显)
* 1.普通方式:不使用任何批量操作,直接用Statement进行操作
* 2.批量处理:使用批量处理,用Statement进行操作
* 3.批量处理:使用批量处理,用PreparedStatement进行操作
* 4.批量处理:使用批量处理,用PreparedStatement进行操作,且
* 当积攒到一定的程度,统一执行一次批量处理操作
* 测试的时候使用Junit工具进行测试
* 在要测试的方法前加入@Test标识,点击方法名右键选择Run As JunitTest
* 测试结果:效率--> 4>3>2>1
* @throws Exception
*/
@Test
public void funtion1() throws Exception
{
/**
* 1.普通方式:不使用任何批量操作,直接用Statement进行操作
* --测试结果:使用时间是:156450
*/
Connection conn = JDBCUtils.getConnection();
String sql = null;
Statement st = null;
try {
st = conn.createStatement();
// 记录程序开始执行的时间
long begin = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
sql = "insert into users values('" + (i + 1) + "','name_" + i + "','pwd_" + i + "')";
// sql= insert into users values( 1,name_0,pwd_0)
st.executeUpdate(sql);
}
// 记录程序结束的时间
long end = System.currentTimeMillis();
System.out.println("使用时间是:" + (end - begin));
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.freeAll(conn, st, null);
}
}
@Test
public void funtion2() throws Exception
{
/**
* 2.批量处理:使用批量处理,用Statement进行操作
* 测试结果:使用时间是:136037
*/
Connection conn = JDBCUtils.getConnection();
String sql = null;
Statement st = null;
try {
st = conn.createStatement();
// 记录程序开始执行的时间
long begin = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
sql = "insert into users values('" + (i + 1) + "','name_" + i + "','pwd_" + i + "')";
// sql= insert into users values( 1,name_0,pwd_0)
st.addBatch(sql);
}
st.executeBatch();
// 记录程序结束的时间
long end = System.currentTimeMillis();
System.out.println("使用时间是:" + (end - begin));
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.freeAll(conn, st, null);
}
}
@Test
public void funtion3() throws Exception
{
/**
* 3.批量处理:使用批量处理,用PreparedStatement进行操作
* 测试结果:使用时间是:1192
*/
Connection conn = JDBCUtils.getConnection();
String sql = "insert into users values(?,?,?)";
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
// 记录程序开始执行的时间
long begin = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
ps.setString(1, String.valueOf((i + 1)));
ps.setString(2, "name_" + i);
ps.setString(3, "pwd_" + i);
ps.addBatch(); // 把sql批量保存
}
ps.executeBatch();// 批量执行sql
long end = System.currentTimeMillis();
System.out.println("使用时间是:" + (end - begin));// 使用时间是:1103
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.freeAll(conn, ps, null);
}
}
@Test
public void funtion4() throws Exception
{
/**
* 4.批量处理:使用批量处理,用PreparedStatement进行操作,
* 且当积攒到一定的程度,统一执行一次批量处理操作
* 使用时间是:758
*/
Connection conn = JDBCUtils.getConnection();
String sql = "insert into users values(?,?,?)";
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
// 记录程序开始执行的时间
long begin = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
ps.setString(1, String.valueOf((i + 1)));
ps.setString(2, "name_" + i);
ps.setString(3, "pwd_" + i);
ps.addBatch(); // 把sql批量保存
// 当积攒到一定程度 统一执行一次sql 然后清空积攒的SQL
if ((i + 1) % 500 == 0) {
ps.executeBatch();
ps.clearBatch();// 清空
}
}
ps.executeBatch();// 批量执行sql
long end = System.currentTimeMillis();
System.out.println("使用时间是:" + (end - begin));// 使用时间是:733
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.freeAll(conn, ps, null);
}
}
}
2.数据库事务相关
<1>事务相关的基础
常见事务相关问题
问题一:什么是事务?
问题二:事务的四大特性是什么?详细解释:ACID
问题三:在数据库中由并发导致的各种问题包含哪些?
问题四:为了解决以上问题设计了哪些隔离级别详细解释?
什么是事务?
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。事务的完成由若干个DML语句组成的。
在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态
事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来,如果回退(ROLLBACK),数据库管理系统将放弃所作的所有修改而回到开始事务时的状态
事务的四大特性?ACID
数据库事务有四大特性: ACID --> 原子性 一致性 隔离性 持久性
原子性(Atomicity)
指事务在逻辑上是不可分割的操作单元,所有的语句要么都执行成功,要么都执行失败并进行撤销。
一致性(Consistency)
从一个状态转换为另外一个状态
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束
隔离性(Isolation)
隔离性是针对并发而言。所谓的并发是指数据库服务器同时执行多个事务,如果在执行的过程中不采取有效的专门控制机制,并发事务之间会发生相互干扰
隔离性就是隔离并发运行的多个事务避免产生相互影响
持久性(Durability)
事务一旦提交对数据的修改就是持久性的,数据已经从内存转移到了外部服务器上,并执行了固化的步骤
在数据库中由并发导致的各种问题包含哪些?
由于数据库的隔离性导致的并发问题包括以下内容
对于同时运行的多个事务,当这些事务访问数据库中相同的数据,如果没有采取必要的隔离机制将会导致各种并发问题。
脏读
对于两个事务T1,T2 。 T1读取了已经被T2更新但是还没有提交的字段之后,如果T2发生了回滚,T1读取的内容就是无效的。
不可重复读
对于两个事务T1,T2。T1读了一个字段,然后T2更新了该字段之后,T1再次读取同一个字段,两次读取的值就是不同的。
幻读(虚读)
对于两个事务T1,T2 T1从一个表中读取一个字段,然后T2在该表中插入一个新的行之后,如果T1再次读取这个表发现数据行数变多。
丢失更新
是第一次提取的内容被第二次提交的内容进行了覆盖
为了解决以上问题设计了哪些隔离级别详细解释?
为了解决并发问题 数据库定义了四种隔离级别。隔离级别越高效率越慢,根据业务选择一个合适的隔离级别,在性能和安全上做平衡。
read_uncommitted:读未提交的数据
允许事务读取未被其他事务提交的变更,脏读,不可重复读,(虚读)幻读问题都会出现
read_commited:读已提交的数据
只允许事务读取已经被其他事务提交的变更,可以避免脏读,但是不可重复读和幻读依然存在
Repeatableread:可重复读
确保事务可以多次从一个字段中读取相同的值,这个事务存在期间,禁止其他事务对这个字段进行更新,可以避免脏读,不可重复读但是幻读依然存在
Serializable:串行化
确保可以从一个表中读取相同的行,这个事务存在期间禁止其他事务对该表进行插入、更新和删除操作。所有的并发问题都可以避免,但是性能十分低下
丢失更新:主要是使用乐观锁和悲观锁解决丢失更新。
并不是所有的数据库都支持这四种隔离级别
Oracle支持两种隔离级别 read_commited
、Serializable
,默认的隔离级别是read_commited
Mysql支持四种隔离级别 mysql的默认隔离级别是repeatableread
1 read uncommited
2 read commited
4 repeatable read
8 serializable
<2>事务的操作
转账测试
利用事务实现转账小程序,在数据库中创建了如下的表
--创建基本账户表
create table banker(
flowId number(10) primary key,
username varchar2(20) not null,
counter number(10) not null
);
/*
假设创建用户张三、李四用户,各自拥有1000元
实现张三转账500给李四
如果转账不成功,两边的账户不该有损失
*/
insert into banker values(1,'张三',1000);
insert into banker values(2,'李四',1000);
select * from banker;
public class TransactionTest {
/**
* 测试:
* 张三向李四转账500元,如果转账出现异常则执行回滚操作进行撤销
* 如无异常则转账成功
* @throws Exception
*/
@Test
public void testTransaction() throws Exception {
Connection conn = JDBCUtils.getConnection();
Statement st = null;
try {
//设置事务取消自动提交
conn.setAutoCommit(false);
st = conn.createStatement();
/**
* 要保证事务的一致性 要么都执行成功要么都失败
* 开启事务 事务默认是自动提交 取消自动提交 改为手动提交
*/
String sql = "update banker set counter=counter-500 where flowId=1";
st.execute(sql);
//设置异常情况
// int i = 10 / 0;
sql = "update banker set counter=counter+500 where flowId=2";
st.execute(sql);
// 执行完毕需要手动提交事务
conn.commit();
} catch (Exception e) {
// 出现异常 回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtils.freeAll(conn, st, null);
}
}
}
如果转账过程中没有出现异常,则正常操作,余额有所变动,但如果转账过程中出现差错,则事务发生回滚,余额不会发生变动
隔离级别测试
public class TransactionTest2 {
/**
* 1 read uncommited
* 2 read commited
* 4 repeatable read
* 8 serializable
* @throws Exception
*/
//测试隔离级别
@Test
public void testTransaction() throws Exception {
//1.得到当前数据库的隔离级别
PreparedStatement ps =null;
Connection conn =JDBCFileUtils.getConnection();
String sql=null;
ResultSet rs =null;
try {
System.out.println(conn.getTransactionIsolation());
//可以通过Connection设置不同的隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
ps=conn.prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}finally{
//得到当前数据库设置的隔离级别
System.out.println(conn.getTransactionIsolation());
}
}
}
<3>数据库连接池
🔖基本概念
场景分析
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
在主程序(如servlet、beans)中建立数据库连接
进行sql操作
断开数据库连接
基于这种模式开发,存在的问题:
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用,若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池的引入
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数
来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量
限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的优势
连接复用:通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等
资源重用:由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
更快的系统反应速度:数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
新的资源分配手段:对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
统一的连接管理,避免数据库连接泄露:在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
📌C3P0数据库连接池
1>导入c3p0相关jar包
搜索c3p0相关信息,可以从c3p0官网上下载,也可以从maven网站进行搜索下载,此外还要下载相应的依赖包
下载完成后加对应jar包加载到指定项目中,随后查看c3p0官网中相关的配置信息
2>利用c3p0创建数据库连接池
在当前工程下创建配置文件c3p0-config.xml
,如果没有指定的路径,默认是在src目录下查找(即如果没有指定配置文件路径则需要将相应的配置文件放置在src目录下)
C3p0-config.xml配置属性
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="helloc3p0">
<!-- 指定c3p0的属性信息 -->
<!-- 数据库的四个属性:用户名、密码、驱动、数据库连接url -->
<property name="user">haha</property>
<property name="password">haha</property>
<property name="jdbcUrl">jdbc:oracle:thin:@localhost:1521:ORCL</property>
<property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
<!-- 下列的属性系统会有相应的默认值,如果没有指定的值,则会使用系统默认的值 -->
<!-- 如果数据库中连接数不足 每次向数据库服务器申请的新的连接的个数 -->
<property name="acquireIncrement">50</property>
<!-- 初始化连接池中 连接的数量 -->
<property name="initialPoolSize">100</property>
<!-- 数据连接池中最小的连接数 -->
<property name="minPoolSize">50</property>
<!-- 数据库连接池最大连接数 -->
<property name="maxPoolSize">1000</property>
<!-- 数据库连接池可维护的Statment数量 -->
<property name="maxStatements">20</property>
<!-- 每个连接数可以使用的最大的对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
1)测试连接
public class C3P0Test {
@Test
public void testC3P0()
{
/**
* 得到c3p0数据库连接池
* 方式1:
* ComboPooledDataSource cpds = new ComboPooledDataSource(连接池名称)
*/
// try {
// ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
// System.out.println(cpds.getConnection());
// } catch (SQLException e) {
// e.printStackTrace();
// }
/**
* 得到c3p0数据库连接池
* 方式2:
* DataSource dataSource = new ComboPooledDataSource(连接池名称);
*/
try {
DataSource dataSource = new ComboPooledDataSource("helloc3p0");
Connection conn = dataSource.getConnection();
System.out.println(conn);
//获取到数据库连接之后则可进行相应的操作
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2)连接成功结果
⚡自定义数据库连接池
自定义数据库连接池的构建思路
1)自定义DataSource实现DataSource接口
2)定义集合容器存储数据库连接对象,重写getConnection()方法(集合操作)
3)上述两步操作可通过初始化操作初始化数据库连接对象,而数据库连接对象的归还(归还到数据库连接池)则需要额外进行操作:
- 思路1:conn.close();关闭数据库连接的时候执行”归还”操作,可通过自定义Connection重写close方法实现(需实现接口方法,在没有其他特殊场景需求扩展的情况下代码看起来较为冗余)
- 思路2:通过动态代理的思路,拦截close方法并执行指定的操作(实现简洁、直观)
4)针对过程中需要自定义Connection、自定义DataSource的情况,可通过装饰者模式、适配器模式优化代码结构,便于更清晰的构建自定义数据库连接池,也避免一些公共功能的冗余
1>案例1:模拟连接池存取
1)自定义连接池类CustomDataSource
/*
自定义连接池类
*/
class CustomDataSource implements DataSource{
// 定义集合容器,用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
// 静态代码块,生成10个数据库连接保存到集合中
static {
for (int i = 0; i < 10; i++) {
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
// 返回连接池的大小
public int getSize() {
return pool.size();
}
// 从连接池中返回一个数据库连接
@Override
public Connection getConnection() {
if(pool.size() > 0) {
// 从连接池中获取数据库连接
return pool.remove(0);
}else {
throw new RuntimeException("数据库连接数量已用尽");
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
2)测试自定义连接池
创建数据表student(sid,sname,sage,sdate)进行测试,此处针对使用完的数据库连接资源直接通过close()关闭连接,而非将数据库连接资源归还连接池中
public class CustomDataSourceDemo {
public static void main(String[] args) throws Exception{
// 创建数据库连接池对象
CustomDataSource dataSource = new CustomDataSource();
System.out.println("使用之前连接池数量:" + dataSource.getSize());
// 获取数据库连接对象
Connection con = dataSource.getConnection();
System.out.println(con.getClass());// JDBC4Connection
// 查询学生表全部信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("sid") + "\t"
+ rs.getString("sname") + "\t"
+ rs.getInt("sage") + "\t"
+ rs.getDate("sdate"));
}
// 释放资源
rs.close();
pst.close();
// 目前的连接对象close方法,是直接关闭连接,而不是将连接归还池中
con.close();
System.out.println("使用之后连接池数量:" + dataSource.getSize());
}
}
2>案例2:自定义Connection(装饰者模式)
结合案例1代码分析可知,在conn执行close的时候只是直接通过close()关闭连接,并将其从线程池中移除,实际上并未真正意义上归还线程连接,而要做到这一点则需要在close中完成连接的归还。核心代码说明如下
1)自定义Connection实现Connection接口
class MyConnection implements Connection {
// 1.定义Connection连接对象和连接池容器对象的变量
private Connection con;
private List<Connection> pool;
// 2.提供有参构造方法,接收连接对象和连接池对象,对变量赋值
public CustomConnection(Connection con,List<Connection> pool) {
this.con = con;
this.pool = pool;
}
// 3.在close()方法中,完成连接的归还
@Override
public void close() throws SQLException {
pool.add(con);
}
// 4.其他重写方法
}
2)自定义DataSource实现DataSource接口,且使用的是CustomConnection
class MyDataSource implements DataSource {
// 1.定义集合容器,用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
// 2.静态代码块,生成10个数据库连接保存到集合中
static {
for (int i = 0; i < 10; i++) {
Connection con = JDBCUtil.getConnection();
pool.add(con);
}
}
// 3.返回连接池的大小
public int getSize() {
return pool.size();
}
// 4.从池中返回一个数据库连接
@Override
public Connection getConnection() {
if(pool.size() > 0) {
// 从池中获取数据库连接
Connection con = pool.remove(0);
// 通过自定义连接对象进行包装
MyConnection cconn = new MyConnection(con,pool);
//返回包装后的连接对象
return cconn;
}else {
throw new RuntimeException("连接数量已用尽");
}
}
// 5.其他重写方法
}
3)测试类
测试类和案例1中的大同小异,此处使用的是MyDataSource创建数据池对象,通过Connection接收得到的是相应的CustomConnection
3>案例3:适配器设计模式
从案例2中可知,如果要实现不同的自定义数据库连接池,不可避免的是需要自定义Connection、DataSource,且实现相应的接口则需重写接口中提供的一系列方法(但实际上要重写的只有那几个关键方法,其余则是根据业务需求调整),可以通过”适配器设计模式”改善代码结构:定义一个适配器实现Connection接口,自定义的连接类只需要通过继承这个适配器并重写需要改造的方法即可,核心代码说明如下
1)CustomConnectionAdapter
abstract class CustomConnectionAdapter implements Connection{
// 1.定义数据库连接对象的变量
private Connection conn;
// 2.通过构造方法赋值
public CustomConnectionAdapter(Connection conn) {
this.conn = conn;
}
/**
* 3.根据指定的数据库服务提供商晚上相应的方法(此处默认用mysql数据库,则直接调用数据库连接对象执行语句即可)
* 如果这些内容没有实现,则通过数据库连接对象调用执行SQL操作的语句则相应会提示异常(例如空指针等)
*/
@Override
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
System.out.println("调用CustomConnectionAdapter的prepareStatement方法");
return conn.prepareStatement(sql);
}
// ...... 其他方法实现 ......
}
2)自定义连接类继承CustomConnectionAdapter,重写核心方法
基于CustomConnectionAdapter构建自定义的Connection,因此不同的Connection容器不需要重复定义公共的一些sql操作方法,只需根据实际业务求重载核心方法即可
/**
* 自定义Connection继承CustomAdapter,重载所需方法
*/
class CustomConnection extends CustomConnectionAdapter{
// 1.定义Connection连接对象和连接池容器对象的变量
private Connection con;
private List<Connection> pool;
// 2.提供有参构造方法,接收连接对象和连接池对象,对变量赋值
public CustomConnection(Connection con,List<Connection> pool) {
super(con); // 将接收的数据库连接对象给适配器父类传递
this.con = con;
this.pool = pool;
}
// 3.在close()方法中,完成连接的归还
@Override
public void close() throws SQLException {
pool.add(con);
}
}
3)自定义DataSource
依次类推,参考自定义Connection的做法,自定义DataSource也可基于适配器设计模式改善
- 自定义DataSource适配器CustomDataSourceAdapter重写核心代码
getConnection()
abstract class CustomDataSourceAdapter implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return null;
}
// ......方法重写......
}
- 定义CustomDataSource继承CustomDataSourceAdapter
class CustomDataSource extends CustomDataSourceAdapter{
// 定义集合容器,用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
// 静态代码块,生成10个数据库连接保存到集合中
static {
for (int i = 0; i < 10; i++) {
Connection con = JDBCUtil.getConnection();
pool.add(con);
}
}
// 返回连接池的大小
public int getSize() {
return pool.size();
}
// 从池中返回一个数据库连接
@Override
public Connection getConnection() {
if(pool.size() > 0) {
// 从池中获取数据库连接
Connection con = pool.remove(0);
// 通过自定义连接对象进行包装
CustomConnection conn = new CustomConnection(con,pool);
// 返回包装后的连接对象
return conn;
}else {
throw new RuntimeException("连接数量已用尽");
}
}
}
4)测试类
public class CustomDataSourceDemo3 {
public static void main(String[] args) throws Exception {
// 创建数据库连接池对象
CustomDataSource dataSource = new CustomDataSource();
System.out.println("使用之前连接池数量:" + dataSource.getSize());
// 获取数据库连接对象
Connection con = dataSource.getConnection();
System.out.println(con.getClass());//
// 查询学生表全部信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("sid") + "\t"
+ rs.getString("sname") + "\t"
+ rs.getInt("sage") + "\t"
+ rs.getDate("sdate"));
}
// 释放资源
rs.close();
pst.close();
con.close();
System.out.println("使用之后连接池数量:" + dataSource.getSize());
}
}
4>案例4:动态代理方式
参考上述案例实现,自定义数据库连接池涉及Connection、DataSource相关的方法重写,虽然采用了装饰者模式、适配器模式修改了代码结构,但还是不可避免代码的编写。回归案例1中连接池的存取,要解决”连接归还至连接池”的问题,最重要的是对conn.close()
方法的重写,通过”动态代理”概念,拦截close方法并指定要执行的操作
构建思路参考案例1中的内容,只不过此处真正对数据库连接的存取做了处理,在getConnection()方法中通过动态代理的方式判断close方法并处理,模拟数据库连接池操作。核心代码参考如下
class ProxyDataSource implements DataSource {
// 定义集合容器,用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());
// 静态代码块,生成10个数据库连接保存到集合中
static {
for (int i = 0; i < 10; i++) {
Connection con = JDBCUtil.getConnection();
pool.add(con);
}
}
// 返回连接池的大小
public int getSize() {
return pool.size();
}
// 动态代理方式实现
@Override
public Connection getConnection() {
if (pool.size() > 0) {
//从池中获取数据库连接
Connection con = pool.remove(0);
Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
/*
执行Connection实现类所有方法都会经过invoke
如果是close方法,则将连接还回池中
如果不是,直接执行实现类的原有方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("close")) {
System.out.println("close方法拦截,将数据库连接归还到连接池中");
pool.add(con);
return null;
} else {
return method.invoke(con, args);
}
}
});
return proxyCon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
// 其余方法重写
}
3.QueryRunner工具类
加载dbutils.jar包到指定的工程
<1>基础语法
--创建worker表简单实现dbutils的测试
create table worker(
wid number(10),
wname varchar2(20) not null,
age number(3) ,
gender varchar2(4),
constraint pk_worker_wid primary key(wid),
constraint ck_worker_age check(age>=0 and age<=150),
constraint ck_worker_gender check(gender='男' or gender='女')
);
--插入数据
insert into worker values(1,'张三',18,'男');
insert into worker values(2,'李四',19,'女');
insert into worker values(3,'王五',20,'男');
insert into worker values(4,'赵六',17,'女');
insert into worker values(5,'田七',20,'男');
select * from worker;
与此相对应的是要创建相应的Model,其属性与数据库中的表的列一一对应,测试则是通过Junit进行单元测试
public class DbutilsTest {
/**
* QueryRunner qr = new QueryRunner();
* 必须保证定义的对象属性与数据库中定义的列的名称、类型一一对应
* 1.查询单个数据 使用ScalarHandler
* 2.查询单个对象 封装为Model对象 一条数据 使用 BeanHandler
* 3.查询单个对象 封装为Map对象 一条数据 使用 MapHandler
* 4.查询多个对象 封装为List<对象类型>对象 使用 BeanListHandler
* 5.查询多个对象 封装为List<Map<String,Object>> 使用 MapListHandler
* 6.增删改 通过QueryRunner的update方法统一实现(方法的整合)
*/
//以下通过几个案例简单说明dbutils的应用
/**
* 案例1.查询所有的Worker对象,并将其封装为List集合
* 封装类型:new BeanListHandler<>(类名.class)
* @throws Exception
*/
@Test
public void testBeanListHandler() throws Exception
{
//1.编写sql语句
String sql="select * from worker";
//2.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//3.获取数据库连接
Connection conn = JDBCUtils.getConnection();
//4.通过QueryRunner对象调用query方法自动进行查询
/**
* 参数1:数据库连接
* 参数2:编写的sql语句
* 参数3:查询后封装的类型
* 只要将查询后需要封装的数据类型及相关参数确定下来,便可以通过
* BeanListHandler进行自动查询封装,对比之前的crud代码,
* 无论是从代码简洁程度还是程序效率都有很大的提高
*/
List<Worker> list = qr.query(conn, sql,new BeanListHandler<>(Worker.class));
//5.打印查询到的数据信息
list.forEach(System.out::println);
}
/**
* 案例2.查询一个Worker对象,并将其封装为Map集合
* worker1={wid=1,wname=张三,age=18,gender=男 }
* worker2={wid=2,wname=李四,age=18,gender=女 }
* ......
* @throws Exception
* 封装类型:new MapHandler()
*/
@Test
public void testBeanMapHandler() throws Exception
{
String sql = "select * from worker where wid=?";
QueryRunner qr = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
/**
* 参数1:数据库连接
* 参数2:编写的sql语句
* 参数3:查询后封装的类型
* 参数4:第四个参数之后的内容是对参数?进行赋值
*/
Map<String,Object> map = qr.query(conn, sql,new MapHandler(),3);
System.out.println(map);
}
/**
* 案例3.查询所有的Worker对象,并将其封装为Map集合
* worker1={wid=1,wname=张三,age=18,gender=男 }
* worker2={wid=2,wname=李四,age=18,gender=女 }
* ......
* @throws Exception
* 封装类型:new MapListHandler()
*/
@Test
public void testBeanMapList() throws Exception
{
String sql = "select * from worker";
QueryRunner qr = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
/**
* 参数1:数据库连接
* 参数2:编写的sql语句
* 参数3:查询后封装的类型
*/
List<Map<String,Object>> listMap = qr.query(conn, sql,new MapListHandler());
listMap.forEach(System.out::println);
//应用:查询员工信息和备注信息,将两个内容映射为一个map集合,然后将加载到list中
}
/**
* 案例4.查询一个对象的某个数据
* 封装类型:
* @throws Exception
*/
@Test
public void testScalarHandle() throws Exception
{
/**
* 查询2号工人的姓名
*/
String sql = "select wname from worker where wid=?";
QueryRunner qr = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
Object obj = qr.query(conn, sql, new ScalarHandler(), 2);
System.out.println(obj);
}
/**
* 案例5.查询单个对象
* 封装类型:
* @throws Exception
*/
@Test
public void testBeanHandler() throws Exception
{
String sql = "select * from worker where wid=?";
QueryRunner qr = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
Worker w = qr.query(conn, sql, new BeanHandler<>(Worker.class), 1);
System.out.println(w);
}
/**
* 案例6.增删改的复合方法
* @throws Exception
* 可通过传入相应的内容进行增删改操作,只要保证传入的参数
* 与编写的sql语句意义对应即可
*/
@Test
public void testQueryRunnerUpdate() throws Exception
{
String sql = "delete from worker where wid in(?,?)";
QueryRunner qr = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
qr.update(conn,sql,2,3);
}
}
<2>通用的DAO
/**
* 通用的DAO设计模式
* 1.设计BaseDAO泛型接口,提供相应的增删改查方法
* a.批量操作方法
* b.查询单个数据
* c.查询单个对象
* d.查询所有对象
* e.增删改方法整合
* 2.设计BaseDAOImpl类用以实现BaseDAO接口
* a.利用反射的概念获取当前传递对象的类型
* b.实现BaseDAO接口中定义的所有抽象方法
* (借助dbutils提供的QueryRunner进行操作)
* 3.设计xxxDAO接口作为实体对象的接口,可以相应地扩展其他方法
* 4.设计xxxDAOImpl类用以继承BaseDAOImpl类并实现xxxDAO接口
* 实现接口中的所有方法,并实现相应的扩展方法
* 5.在进行crud测试的时候只需要提供相应的参数即可
* a.数据库连接:Connection conn
* b.执行语句:String sql
* c.传递参数:根据sql的设定传入相应的参数列表
*/
/**
* 访问数据的DAO的接口
* 此处定义访问数据表的各种方法
*/
public interface BaseDAO<T> {
/**
* 定义批量处理的方法
* @param conn
* @param sql
* @param args:填充占位符,不定长参数
* @throws SQLException
*/
public void batch(Connection conn,String sql,Object[]... args)throws SQLException;
/**
* 定义返回一个具体的值的方法
* 例如查询某个人的某个信息、查询总人数、查询平均工资等
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public <E> E getForValue(Connection conn,String sql,Object... args)throws SQLException;
/**
* 查询一个对象的集合,返回T集合的所有对象
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public List<T> getForList(Connection conn,String sql,Object... args)throws SQLException;
/**
* 返回查询的一个T类型的对象
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public T get(Connection conn,String sql,Object... args)throws SQLException;
/**
* 增删改的通用方法
* @param conn
* @param sql
* @param args
* @throws SQLException
*/
public void update(Connection conn,String sql,Object... args)throws SQLException;
}
/**
* 定义实现BaseDAO的实现类
*/
public class BaseDAOImpl<T> implements BaseDAO<T>{
private QueryRunner qr = null;
/**
* 利用反射实现得到对象的类型
* 此处简单了解,之后在第二阶段再深入学习有关反射的概念
* 注意导入的包是import java.lang.reflect.Type;
*/
private Class<T> type;
public BaseDAOImpl() {
qr = new QueryRunner();
Type genType =getClass().getGenericSuperclass();
Type [] params= ((ParameterizedType)genType).getActualTypeArguments();
type=(Class) params[0];
}
@Override
public void batch(Connection conn, String sql, Object[]... args)
throws SQLException {
qr.batch(conn, sql, args);
}
@Override
public <E> E getForValue(Connection conn, String sql, Object... args)
throws SQLException {
return (E) qr.query(conn, sql, new ScalarHandler(),args);
}
@Override
public List<T> getForList(Connection conn, String sql, Object... args)
throws SQLException {
return qr.query(conn, sql, new BeanListHandler<>(type), args);
}
@Override
public T get(Connection conn, String sql, Object... args)
throws SQLException {
return qr.query(conn, sql, new BeanHandler<>(type), args);
}
@Override
public void update(Connection conn, String sql, Object... args)
throws SQLException {
qr.update(conn, sql, args);
}
}
public interface WorkerDAO extends BaseDAO<Worker> {
/**
* WorkerDAO中除继承BaseDAO外还可扩展自己的特性
*/
//定义分页方法
public void showPageData(String pageSize,String pageNum);
}
public class WorkerDAOImpl extends BaseDAOImpl<Worker> implements WorkerDAO{
/**
* WorkerDAOImpl中除继承BaseDAOImpl外还需要
* 实现WorkerDAO扩展的方法
*/
@Override
public void showPageData(String pageSize, String pageNum) {
//方法的实现
}
}
public class WorkerTest {
/**
* 测试Worker的增删改查
* @throws Exception
* 完成基本模式的设计,在调用相应的测试方法只需要考虑
* 传入3个参数,其分别为Connection连接、sql语句
* 以及sql语句所需要的参数
*/
@Test
public void testAddWorker() throws Exception
{
WorkerDAO wd = new WorkerDAOImpl();
Connection conn = JDBCUtils.getConnection();
String sql = "insert into worker values(?,?,?,?)";
Object[] args={3,"王五",20,"女"};
wd.update(conn, sql, args);
}
@Test
public void queryAllWorker() throws Exception
{
WorkerDAO wd = new WorkerDAOImpl();
Connection conn = JDBCUtils.getConnection();
String sql = "select * from worker";
List<Worker> list = wd.getForList(conn, sql);
list.forEach(System.out::println);
}
}
<3>JDBC调用存储过程
public class TestPlsql {
/**
* 调用存储过程
* @throws Exception
*/
@Test
public void callProcedure() throws Exception
{
/**
* sql语句定义格式:
* 用一堆花括号包含整个整体{call 存储过程名称(参数列表)}
* eg:{call test(?,?)}
*/
String sql = "{call testAdd(?,?,?)}";
Connection conn = JDBCUtils.getConnection();
CallableStatement cs = conn.prepareCall(sql);
//传入相关的参数
cs.setInt(1, 100);
cs.setInt(2, 50);
/**
* 注册输出参数
* cs.registerOutParameter(第几个参数, 参数类型);
*/
cs.registerOutParameter(3, Types.INTEGER);
cs.execute();
//获取输出参数并打印
int result = cs.getInt(3);
System.out.println(result);
}
/**
* 调用函数
* @throws Exception
*/
@Test
public void callFuction() throws Exception
{
/**
* sql语句定义格式:
* 用一堆花括号包含整个整体{返回值=函数名称(参数列表)}
* eg:{?=call test(?,?)}
*/
String sql ="{?= call testAdd2(?,?)}";
Connection conn = JDBCUtils.getConnection();
CallableStatement cs = conn.prepareCall(sql);
//传入相关的参数
cs.setInt(2, 100);
cs.setInt(3, 50);
//注册输出参数
cs.registerOutParameter(1, Types.INTEGER);
cs.execute();
//获取输出参数
System.out.println(cs.getInt(1));
}
}