JDBC
JDBC概述
客户端操作数据库的方式
1、方式1: 使用第三方客户端来访问 MySQL:SQLyog
2、方式2: 使用命令行
3、通过 Java程序 来访问 MySQL 数据库(JDBC)
JDBC概念
JDBC(Java Data Base Connectivity) 是 Java 访问数据库的标准规范。
是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
JDBC原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eWcCQg5i-1621589561592)(E:\MarkDown\拉勾笔记\JDBC原理)]
JDBC就是由sun公司定义的一套操作所有关系型数据库的规则(接口),而数据库厂商需要实现这套接口,提供数据库驱动jar包,我们可以使用这套接口编程,真正执行的代码是对应驱动包中的实现类。
JDBC开发
数据准备
-- 创建 jdbc_user表
CREATE TABLE jdbc_user (
id INT PRIMARY KEY AUTO_INCREMENT ,
username VARCHAR(50),
PASSWORD VARCHAR(50),
birthday DATE
);
-- 添加数据
INSERT INTO jdbc_user (username, PASSWORD,birthday)
VALUES('admin1', '123','1991/12/24'),
('admin2','123','1995/12/24'),
('test1', '123','1998/12/24'),
('test2', '123','2000/12/24');
MySQL驱动包
1、将MySQL驱动包添加到jar包库文件夹中,myJar文件夹,用于存放当前项目需要的所有jar包(E:\myJar)
2、在idea中配置jar包库的位置
API使用: 1.注册驱动
1、JDBC规范定义驱动接口:java.sql.Driver
2、MySql驱动包提供了实现类:com.mysql.jdbc.Driver
3、加载注册驱动的方式:Class.forName(数据库驱动实现类)——加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供–“com.mysql.jdbc.Driver”
public class JDBCDemo01 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
}
}
4、 为什么可以使用Class.forName方式注册驱动?
// Driver类是由MySql驱动包提供的一个实现类,它实现了java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
// 静态代码块,随着类的加载而加载,只加载一次
static {
try {
// DriverManager类就是驱动管理类
// registerDriver()方法就是用来注册驱动的
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
5、注意
从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。 Class.forName 这句话可以省略。
API使用: 2.获得连接
1、Connection接口,代表一个连接对象,具体的实现类由数据库的厂商实现
2、使用DriverManager类的静态方法,getConnection可以获取数据库的连接
获取连接的静态方法 | 说明 |
---|---|
Connection getConnection(String url, String user, String password) | 通过连接字符串和用户名,密码来获取数据库连接对象 |
a、getConnection方法3个连接参数说明
连接参数 | 说明 |
---|---|
user | 登录用户名 |
password | 登录密码 |
url | mySql URL的格式 jdbc:mysql://localhost:3306/db4 |
b、对URL的详细说明
jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔。
第一部分:协议 jdbc,这是固定的
第二部分:子协议,就是数据库名称,连接mysql数据库
第三部分:由数据库厂商规定,我们需要了解每个数据库厂商的要求,mysql的第三部分由数据库服务器的IP地址(localhost)、端口号(3306),以及要使用的数据库名称组成
public class JDBCDemo01 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接 url 用户名 密码
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding = UTF-8";
Connection con = DriverManager.getConnection(url, "root", "123456");
// 打印连接对象 com.mysql.jdbc.JDBC4Connection@56cbfb61
System.out.println(con);
}
}
API 使用: 3.获取语句执行平台
1、通过Connection 的createStatement方法获取sql语句执行对象
Connection接口中的方法 | 说明 |
---|---|
Statement createStatement() | 创建SQL语句执行对象 |
2、Statement : 代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象
Statement类 常用方法 | 说明 |
---|---|
int executeUpdate(String sql); | 执行insert update delete语句。返回int类型,代表受影响的行数 |
ResultSet executeQuery(String sql); | 执行select语句,返回ResultSet结果集对象 |
public class JDBCDemo01 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接 url 用户名 密码
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding = UTF-8";
Connection con = DriverManager.getConnection(url, "root", "123456");
// 打印连接对象 com.mysql.jdbc.JDBC4Connection@56cbfb61
//System.out.println(con);
// 3.获取语句执行平台 Statement
Statement statement = con.createStatement();
// 3.1.通过Statement对象的executeUpdate方法创建一张表
String sql = "create table test(id int, name varchar(20), age int);";
int i = statement.executeUpdate(sql); // 返回值是int类型表示受影响的行数
System.out.println(i);
// 4.关闭流
statement.close();
con.close();
}
}
API 使用: 4.处理结果集
只有在进行查询操作的时候, 才会处理结果集
ResultSet接口
作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。
ResultSet接口方法 | 说明 |
---|---|
boolean next() | 1) 判断是否有下一条数据,游标向下一行 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false |
xxx getXxx( String or int) | 1) 通过列名,参数是 String 类型。返回不同的类型 2) 通过列号,参数是int类型,从 1 开始。返回不同的类型 |
int getInt(“列名”);
int getInt(1);——表示列号
String getString(“列名”);
String getString(1);——1表示列号
public class JDBCDemo02 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db4", "root", "123456");
// 3.获取语句执行平台 Statement
Statement statement = con.createStatement();
// 4.执行查询操作
// 4.1使用executeQuery()
String sql = "select * from jdbc_user";
// resultSet是结果集对象
ResultSet resultSet = statement.executeQuery(sql);
// 通过where循环 遍历获取resultSet中的数据
while(resultSet.next()){
// 获取id
int id = resultSet.getInt("id");
// 获取姓名
String username = resultSet.getString("username");
// 获取密码
String password = resultSet.getString("password");
// 获取生日
Date birthday = resultSet.getDate("birthday");
System.out.println(id + " : " + username + " : " + password + " : " + birthday);
}
// 5.关闭流
resultSet.close();
statement.close();
con.close();
}
}
// 4.2处理结果集对象
// boolean next = resultSet.next(); // 判断是否有下一条数据
// System.out.println(next);
// 获取id
// int id = resultSet.getInt("id");
// System.out.println("通过列名获取的id为:" + id);
// int anInt = resultSet.getInt(1);
// System.out.println("通过列号获取的id为:" + id);
API 使用: 5.释放资源
1、需要释放的对象:ResultSet 结果集(只有查询会用到),Statement 语句,Connection 连接
2、释放原则:先开后关,后开先关。ResultSet ==> Statement ==> Connection
3、放在哪个代码块中:finally 块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hq5AxCxb-1621589561599)(E:\MarkDown\拉勾笔记\JDBC释放资源)]
步骤总结
1、获取驱动(可以省略)
2、获取连接
3、获取Statement对象
4、处理结果集(只在查询时处理)
5、释放资源
public class JDBCDemo03 {
public static void main(String[] args) {
Connection con = null;
Statement statement = null;
ResultSet resultSet = null;
String sql = "jdbc:mysql://localhost:3306/db4";
try {
// 1.注册驱动 省略
// 2.获取连接
con = DriverManager.getConnection(sql, "root", "123456");
// 3.获取语句执行对象
statement = con.createStatement();
// 4.执行sql
String sql1 = "select * from jdbc_user";
resultSet = statement.executeQuery(sql1);
// 5.处理结果集对象
} catch (SQLException e) {
e.printStackTrace();
} finally {
// finally 中的代码始终会执行
// 关闭资源
try {
resultSet.close();
statement.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC实现增删改查
JDBC工具类
1、什么时候自己创建工具类?
a、如果一个功能经常要用到,建议把这个功能做成一个工具类,可以在不同的地方重用。
b、“获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。
2、工具类包含的内容
a、可以把几个字符串定义成常量:用户名,密码,URL,驱动类
b、得到数据库的连接:getConnection()
c、关闭所有打开的资源:
public class JDBCUtils {
// 1.将连接信息定义为 字符串常量
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
public static final String USER = "root";
public static final String PASSWORD = "123456";
// 2.编写静态代码块
static{
try {
// 1.注册驱动
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 3.获取连接的 静态方法
public static Connection getConnection(){
// 获取连接对象并返回
Connection connection = null;
try {
connection = DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
// 4.关闭资源的方法
public static void close(Connection con, Statement statement){
if (con != null && statement != null){
try {
statement.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement statement, ResultSet resultSet){
if (con != null && statement != null){
try {
statement.close();
con.close();
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
DML操作
public class TestDML {
/**
* 插入数据
*/
@Test
public void testInsert() throws SQLException {
// 1.通过JDBCUtils工具类 获取连接
Connection con = JDBCUtils.getConnection();
// 2.获取Statement 对象
Statement statement = con.createStatement();
// 2.1编写SQL
String sql = "insert into jdbc_user values(null,'张百万','123','2020/11/11')";
// 2.2执行SQL
int i = statement.executeUpdate(sql);
System.out.println(i);
// 3.关闭流
JDBCUtils.close(con,statement);
}
/**
* 更新操作
* 根据id修改用户名
*/
@Test
public void testUpdate() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "update jdbc_user set username = '刘能' where id = 1";
statement.executeUpdate(sql);
JDBCUtils.close(connection,statement);
}
/**
* 删除操作
* 删除id 为1 和 2的数据
*/
@Test
public void testDelete() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "delete from jdbc_user where id in(1,2)";
statement.executeUpdate(sql);
JDBCUtils.close(connection,statement);
}
}
DQL操作
public class TestDQL {
// 查询姓名为张百万的一条记录
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = JDBCUtils.getConnection();
// 2.创建Statement 对象
Statement statement = con.createStatement();
// 3.编写SQL
String sql = "select * from jdbc_user where username = '张百万'";
ResultSet resultSet = statement.executeQuery(sql);
// 4.处理结果集
while(resultSet.next()){
// 通过列名方式获取
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
Date birthday = resultSet.getDate("birthday");
System.out.println(id + ":" + username + ":" + password + ":" + birthday);
}
// 5.是否资源
JDBCUtils.close(con,statement,resultSet);
}
}
SQL注入问题
SQL注入演示
# 插入2条数据
INSERT INTO jdbc_user VALUES(NULL,'jack','123456','2020/2/24');
INSERT INTO jdbc_user VALUES(NULL,'tom','123456','2020/2/24');
-- 查询用户操作
SELECT * FROM jdbc_user WHERE username = 'tom' AND PASSWORD = '123456';
-- SQL注入问题演示
SELECT * FROM jdbc_user WHERE username = 'tom' AND PASSWORD = '123' OR '1' = '1';
sql注入案例:用户登陆
/**
* @auther weiwei
* @date 2021/5/17 16:10
* @description 用户登陆案例
*/
public class TestLogin01 {
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = JDBCUtils.getConnection();
// 2.创建Statement 对象
Statement statement = con.createStatement();
// 3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
// 4.拼接SQL语句
String sql = "select * from jdbc_user where username = '" + name +"' and password = '" + password + "'";
System.out.println(sql);
// 5.执行查询 获取结果集对象
ResultSet resultSet = statement.executeQuery(sql);
// 6.处理结果集
if (resultSet.next()){
System.out.println("登陆成功!欢迎您:" + name);
} else {
System.out.println("登陆失败!");
}
// 关闭流
JDBCUtils.close(con,statement,resultSet);
}
}
// SQL注入结果
请输入用户名:
zbc
请输入密码:
zbc' or '1' = '1
select * from jdbc_user where username = 'zbc' and password = 'zbc'or '1' = '1'
登陆成功!欢迎您:zbc
问题分析
1、什么是SQL注入?
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正意义,即为SQL注入。
2、如何实现注入
根据用户输入的数据,拼接处的字符串
select * from jdbc_user where username = 'zbc' and password = 'zbc'or '1' = '1'
name = 'zbc' and password = 'abc' 为假, '1' = '1'真
相当于 select * from user where true = true; 查询了所有记录
3、要如何解决
不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接
预处理对象
PreparedStatement 接口介绍
1、PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句对象
2、预编译: 是指SQL 语句被预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句
PreparedStatement 特点
1、因为有预先编译的功能,提高SQL的执行效率。
2、可以有效的防止SQL注入的问题,安全性更高
获取PreparedStatement对象
通过Connection创建PreparedStatement对象
Connection 接口中的方法 | 说明 |
---|---|
PreparedStatement prepareStatement(String sql) | 指定预编译的 SQL 语句,SQL 语句中使用占位符 ? 创建一个语句对象 |
PreparedStatement接口常用方法
常用方法 | 说明 |
---|---|
int executeUpdate(); | 执行insert update delete语句 |
ResultSet executeQuery(); | 执行select语句。返回结果集对象 Resulet |
使用PreparedStatement的步骤
1、 编写SQL语句,未知内容使用?占位:
2、获得 PreparedStatement 对象
3、设置实际参数:setXxx( 占位符的位置, 真实的值
4、执行参数化SQL语句
5、关闭资源
setXxx重载方法 | 说明 |
---|---|
void setDouble(int parameterIndex, double x) | 将指定参数设置为给定 Java double 值 |
void setInt(int parameterIndex, int x) | 将指定参数设置为给定 Java int 值 |
void setString(int parameterIndex, String x) | 将指定参数设置为给定 Java String 值 |
void setObject(int parameterIndex, Object x) | 使用给定对象设置指定参数的值 |
使用PreparedStatement完成登录案例
用 PreparedStatement 预处理对象,可以有效的避免SQL注入
步骤:
1.获取数据库连接对象
2.编写SQL 使用? 占位符方式
3.获取预处理对象 (预编译对象会将Sql发送给数据库 进行预编译)
4.提示用户输入用户名 & 密码
5.设置实际参数:setXxx(占位符的位置, 真实的值)
6.执行查询获取结果集
7.判断是否查询到数据
8.关闭资源
public class TestLogin02 {
/**
* SQL注入
* 用户输入的用户名和密码 与我们编写的SQL进行了拼接,用户输入的内容成为了SQL语法的一部分,
* 用户会利用这里的漏洞输入一些其他的字符串,改变SQL原有的意思
*
* 如何解决
* 要解决SQL注入 就不能让用户输入的数据和我们的SQL语句进行直接的拼接
*
* 预处理对象 PreparedStatement 是Statement接口的子接口
* 使用预处理对象 有预编译功能,提高SQL的执行效率
* 使用预处理对象 通过占位符的方式,设置参数,可以有效防止SQL注入
* @param args
*/
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = JDBCUtils.getConnection();
// 2.获取PrepareStatement 预处理对象
// 使用 ? 占位符的方式来设置参数
String sql = "select * from jdbc_user where username = ? and password = ?";
PreparedStatement ps = con.prepareStatement(sql);
// 3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
// 4.设置参数 使用 setXXX(占位符的位置(整数),要设置的值) 的方法设置占位符的参数
ps.setString(1,name); // 设置第一个问号值为 name
ps.setString(2,password); // 设置第二个问号值为 password
// 5.执行查询
ResultSet resultSet = ps.executeQuery();
// 6.处理结果集
if (resultSet.next()){
System.out.println("登陆成功!欢迎您:" + name);
} else {
System.out.println("登陆失败!");
}
// 7.关闭流
JDBCUtils.close(con,ps,resultSet);
}
}
PreparedStatement的执行原理
public class TestPS {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
// 获取Statement
// Statement对象每执行一条SQL,就会发送给数据库,数据库先编译再执行。即每次运行都需要编译,效率低
Statement statement = connection.createStatement();
// 向数据库插入两条数据
statement.executeUpdate("insert into jdbc_user values(null,'张三','123456','2021/5/17')");
statement.executeUpdate("insert into jdbc_user values(null,'李四','654321','2020/5/17')");
// 获取预处理对象PrepareStatement
// 预处理对象会将SQL发送给数据库进行一个预编译,然后将预编译的SQL保存起来(缓存),
// 这样只需要编译一次,当执行多次插入操作时,只需要设置参数就可以了
PreparedStatement ps = connection.prepareStatement("insert into jdbc_user values(?,?,?,?)");
// 先插入第一条数据
ps.setObject(1,null);
ps.setString(2,"小斌");
ps.setString(3,"qwer");
ps.setString(4,"1999/11/11");
//执行插入
ps.executeUpdate();
// 插入第二条数据
ps.setObject(1,null);
ps.setString(2,"长海");
ps.setString(3,"asdf");
ps.setString(4,"2000/11/11");
//执行插入
ps.executeUpdate();
// 释放资源
statement.close();
ps.clearParameters();
connection.close();
}
}
Statement 与 PreparedStatement的区别?
1、Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句
2、PrepareStatement是预编译的SQL语句对象,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值
3、PrepareStatement可以减少编译次数提高数据库性能
JDBC控制事务
数据准备
-- 创建账户表
CREATE TABLE account(
-- 主键
id INT PRIMARY KEY AUTO_INCREMENT,
-- 姓名
NAME VARCHAR(10),
-- 转账金额
money DOUBLE
);
-- 添加两个用户
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);
事务相关API
使用 Connection中的方法实现事务管理
方法 | 说明 |
---|---|
void setAutoCommit(boolean autoCommit) | 参数是true或false如果设置为false,表示关闭自动提交,相当于开启事务 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
开发步骤
1、获取连接
2、开启事务
3、获取到 PreparedStatement , 执行两次更新操作
4、正常情况下提交事务
5、出现异常回滚事务
6、最后关闭资源
代码示例
public class TestJDBCTransaction {
// 使用JDBC操作事务
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
try {
// 1.获取连接
con = JDBCUtils.getConnection();
// 2.开启事务
con.setAutoCommit(false); // 手动提交事务
// 3.获取预处理对象,执行SQL(来你猜修改操作)
// 3.1 tom账户 - 500
ps = con.prepareStatement("update account set money = money - ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"tom");
ps.executeUpdate();
// 模拟 tom转账之后出现异常
//System.out.println(1 / 0);
// 3.2 Jack账户 + 500
ps = con.prepareStatement("update account set money = money + ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"jack");
ps.executeUpdate();
// 4.提交事务(正常情况)
con.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
e.printStackTrace();
// 5.出现异常就回滚
try {
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 6.释放资源
JDBCUtils.close(con,ps);
}
}
}
数据库连接池和DBUtils
数据库连接池
连接池介绍
1、连接池概念
实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采用连接池技术,来共享连接Connection。这样我们就不需要每次都创建连接、释放连接了,这些操作都交给了连接池。
2、连接池的好处
用池来管理Connection,这样可以重复使用Connection。 当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection归还给池。
JDBC方式与连接池方式
1、普通 JDBC方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jf5XfTFU-1621589561605)(E:\MarkDown\拉勾笔记\普通JDBC方式)]
2、连接池方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5t8BMbiy-1621589561611)(E:\MarkDown\拉勾笔记\连接池方式)]
如何使用数据库连接池
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池:DBCP连接池,C3P0连接池,Druid连接池
数据准备
#创建数据库
CREATE DATABASE db5 CHARACTER SET utf8;
#使用数据库
USE db5;
#创建员工表
CREATE TABLE employee (
eid INT PRIMARY KEY AUTO_INCREMENT ,
ename VARCHAR (20), -- 员工姓名
age INT , -- 员工年龄
sex VARCHAR (6), -- 员工性别
salary DOUBLE , -- 薪水
empdate DATE -- 入职日期
);
#插入数据
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李清照',22,'女',4000,'2018-11-12');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'林黛玉',20,'女',5000,'2019-03-14');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'杜甫',40,'男',6000,'2020-01-01');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李白',25,'男',3000,'2017-10-01');
DBCP连接池
DBCP也是一个开源的连接池,是Apache成员之一,在企业开发中也比较常见,tomcat内置的连接池。
创建项目 导入 jar包
1、将两个 jar包添加到 myJar文件夹中
2、 添加myJar库 到项目的依赖中
编写工具类
连接数据库表的工具类,采用DBCP连接池的方式来完成
a、Java中提供了一个连接池的规则接口 : DataSource , 它是java中提供的连接池
b、在DBCP包中提供了DataSource接口的实现类,我们要用的具体的连接池 BasicDataSource 类
public class DBCPUtils {
// 1.定义常量,保存数据库连接信息
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/db5?characterEncoding=UTF-8";
public static final String USERNAME = "root";
public static final String PASSWORD = "123456";
// 2.创建连接池对象(由DBCP提供的实现类)
public static BasicDataSource dataSource = new BasicDataSource();
// 3.使用静态代码块进行配置
static {
dataSource.setDriverClassName(DRIVERNAME);
dataSource.setUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
}
// 4.获取连接的方法
public static Connection getConnection() throws SQLException {
// 从连接池中获取连接
Connection connection = dataSource.getConnection();
return connection;
}
// 5.释放资源
public static void close(Connection con, Statement statement){
if (con != null && statement != null){
try {
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement statement, ResultSet resultSet){
if (con != null && statement != null && resultSet != null){
try {
resultSet.close();
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试工具类
public class TestDBCP {
/**
* 测试DBCP连接池
* @param args
*/
public static void main(String[] args) throws SQLException {
// 1.从DBCP连接池中拿到连接
Connection con = DBCPUtils.getConnection();
// 2.获取Statement对象
Statement statement = con.createStatement();
// 3.查询所有员工的姓名
String sql = "select ename from employee";
ResultSet resultSet = statement.executeQuery(sql);
// 4.处理结果集
while (resultSet.next()){
String ename = resultSet.getString("ename");
System.out.println("员工姓名:" + ename);
}
// 5.释放资源
DBCPUtils.close(con,statement,resultSet);
}
}
常见配置项
属性 | 描述 |
---|---|
driverClassName | 数据库驱动名称 |
url | 数据库地址 |
username | 用户名 |
password | 密码 |
maxActive | 最大连接数量 |
maxIdle | 最大空闲连接 |
minIdle | 最小空闲连接 |
initialSize | 初始化连接 |
C3P0连接池
C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等
导入jar包及配置文件
1、 将jar包复制到myJar文件夹即可,IDEA会自动导入
2、导入配置文件 c3p0-config.xml
c3p0-config.xml 文件名不可更改
直接放到src下,也可以放到到资源文件夹中
3、 在项目下创建一个resource文件夹(专门存放资源文件)
4、选择文件夹,右键将resource文件夹指定为资源文件夹
5、将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件
编写C3P0工具类
C3P0提供的核心工具类,ComboPooledDataSource,如果想使用连接池,就必须创建该类的对象
new ComboPooledDataSource(); 使用默认配置
new ComboPooledDataSource(“mysql”); 使用命名配置
public class C3P0Utils {
// 1.创建连接池对象 C3P0对DataSource接口的实现类
// 使用的配置 是配置文件中的默认配置
//public static ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 2.使用指定的配置
public static ComboPooledDataSource dataSource = new ComboPooledDataSource("mysql");
// 3.获取连接的方法
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 4.释放资源
public static void close(Connection con, Statement statement){
if (con != null && statement != null){
try {
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement statement, ResultSet resultSet){
if (con != null && statement != null && resultSet != null){
try {
resultSet.close();
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试工具类
public class TestC3P0 {
// 需求:查询姓名为李白的记录
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = C3P0Utils.getConnection();
// 2.获取预处理对象
String sql = "select * from employee where ename = ?";
PreparedStatement ps = con.prepareStatement(sql);
// 3.设置占位符的值
ps.setString(1,"李白");
ResultSet resultSet = ps.executeQuery();
// 4.处理结果集
while(resultSet.next()){
int eid = resultSet.getInt("eid");
String ename = resultSet.getString("ename");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
double salary = resultSet.getDouble("salary");
Date date = resultSet.getDate("empdate");
System.out.println(eid + " " + ename + " " + age + " " + sex + " " + salary + " " + date);
}
// 5.释放资源
C3P0Utils.close(con,ps,resultSet);
}
}
常见配置
分类 | 属性 | 描述 |
---|---|---|
必须项 | user password driverClass jdbcUrl | 用户名 密码 驱动 路径 |
基本配置 | initialPoolSize maxPoolSize minPoolSize maxIdleTime | 连接池初始化时创建的连接数,默认值:3 连接池中拥有最大的连接数,默恩值:15 连接池保持的最小连接数。10 连接的最大空闲时间。如果超过这个时间,某个数据库连接还没有被使用,则会断开这个连接,如果为0,则永远不会断开连接。默认值:0 |
Druid连接池
Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。
导入jar包及配置文件
1、导入 jar包
2、导入配置文件
是properties形式的
可以叫任意名称,可以放在任意目录下,我们统一放到 resources资源目录
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db5?characterEncoding=UTF-8
username=root
password=123456
initialSize=5
maxActive=10
maxWait=3000
编写Druid工具类
获取数据库连接池对象
通过工厂来来获取 DruidDataSourceFactory类的createDataSource方法
createDataSource(Properties p) 方法参数可以是一个属性集对象
public class DruidUtils {
// 1.定义成员变量
public static DataSource dataSource;
// 2.静态代码块
static {
try {
// 3.创建属性集对象
Properties p = new Properties();
// 4.加载配置文件 Druid 连接池不能主动加载配置文件,需要指定文件
InputStream inputStream = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
// 5.使用Properties对象的 load方法 从字节流中读取配置信息
p.load(inputStream);
// 6.通过工厂类获取连接池对象
dataSource = DruidDataSourceFactory.createDataSource(p);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接的方法
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
// 获取Druid连接池对象的方法
public static DataSource getDataSource(){
return dataSource;
}
// 释放资源
public static void close(Connection con, Statement statement){
if (con != null && statement != null){
try {
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement statement, ResultSet resultSet){
if (con != null && statement != null && resultSet != null){
try {
resultSet.close();
statement.close();
// 归还连接
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试工具类
public class TestDruid {
// 需求:查询薪资在3000到5000之间的员工的姓名
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = DruidUtils.getConnection();
// 2.获取Statement对象
Statement statement = con.createStatement();
// 3.执行查询
ResultSet resultSet = statement.executeQuery("select ename from employee where salary between 3000 and 5000");
// 4.处理结果集
while(resultSet.next()){
String ename = resultSet.getString("ename");
System.out.println(ename);
}
// 5.释放资源
DruidUtils.close(con,statement,resultSet);
}
}
DBUtils工具类
DBUtils简介
使用JDBC我们发现冗余的代码太多了,为了简化开发 我们选择使用 DbUtils。
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
使用方式:
DBUtils就是JDBC的简化开发工具包。需要项目导入 commons-dbutils-1.6.jar。
Dbutils核心功能介绍
1、QueryRunner 中提供对sql语句操作的API
2、ResultSetHandler接口,用于定义select操作后,怎样封装结果集
3、DbUtils类,他就是一个工具类,定义了关闭资源与事务处理相关方法
案例相关知识
表和类之间的关系
1、整个表可以看做是一个类
2、表中的一行记录,对应一个类的实例(对象)
3、表中的一列,对应类中的一个成员属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aL82KbWL-1621589561615)(E:\MarkDown\拉勾笔记\表和类之间的关系)]
JavaBean组件
1、JavaBean就是一个类,开发中通常用于封装数据,有以下特点
a.需要实现序列化接口,Serializable (暂时可以省略)
b.提供私有字段: private 类型 变量名;
c.提供getter和setter
d.提供空参构造
2、创建Employee类和数据库的employee表对应
创建一个 entity包,专门用来存放 JavaBean类
/**
* @auther weiwei
* @date 2021/5/18 15:56
* @description JavaBean类
* 用来存储数据 成员变量私有 提供get set 提供空参构造器 实现序列化接口
*
* Employee类 对应数据库中的 employee表
* `eid` int(11) NOT NULL AUTO_INCREMENT,
* `ename` varchar(20) DEFAULT NULL,
* `age` int(11) DEFAULT NULL,
* `sex` varchar(6) DEFAULT NULL,
* `salary` double DEFAULT NULL,
* `empdate` date DEFAULT NULL,
*/
public class Employee implements Serializable {
// 成员变量名称 与 表中的列要一样
private int eid;
private String ename;
private int age;
private String sex;
private double salary;
private Date empdaye;
public Employee() {
}
public Employee(int eid, String ename, int age, String sex, double salary, Date empdaye) {
this.eid = eid;
this.ename = ename;
this.age = age;
this.sex = sex;
this.salary = salary;
this.empdaye = empdaye;
}
public int getEid() {
return eid;
}
public void setEid(int eid) {
this.eid = eid;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getEmpdaye() {
return empdaye;
}
public void setEmpdaye(Date empdaye) {
this.empdaye = empdaye;
}
@Override
public String toString() {
return "Employee{" +
"eid=" + eid +
", ename='" + ename + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", salary=" + salary +
", empdaye=" + empdaye +
'}';
}
}
DBUtils完成 CRUD
QueryRunner核心类
1、构造方法
QueryRunner()
QueryRunner(DataSource ds),提供数据源(连接池),DBUtils底层自动维护连接connection
2、常用方法
update(Connection conn, String sql, Object… params) ,用来完成表数据的增加、删除、更新操作
query(Connection conn, String sql, ResultSetHandler rsh, Object… params) ,用来完成表
数据的查询操作
QueryRunner的创建
public class DBUtilsDemo01 {
// QueryRunner 核心类的创建方式
public static void main(String[] args) {
// 方式1:手动模式
QueryRunner qr = new QueryRunner();
// 方式2:自动模式 提供数据库连接池对象,DBUtils会自动的维护连接
QueryRunner qr2 = new QueryRunner(DruidUtils.getDataSource());
}
}
QueryRunner实现增、删、改操作
核心方法:update(Connection conn, String sql, Object… params)
参数 | 说明 |
---|---|
Connection conn | 数据库连接对象,自动模式创建QueryRun 可以不传,手动模式必须传递 |
String sql | 占位符形式的SQL,使用 ? 号占位符 |
Object… param | Object类型的可变参,用来设置占位符上的参数 |
步骤
1.创建QueryRunner(手动或自动)
2.占位符方式 编写SQL
3.设置占位符参数
4.执行
/**
* @auther weiwei
* @date 2021/5/18 16:26
* @description 使用QueryRunner对象 完成增删改
* update(Connection con, String sql, Object...param)方法
*/
public class DBUtilsDemo02 {
// 插入操作
@Test
public void testInsert() throws SQLException {
// 1.创建QueryRunner 手动模式创建
QueryRunner qr = new QueryRunner();
// 2.编写 占位符方式 SQL
String sql = "insert into employee values(?,?,?,?,?,?)";
// 3.设置占位符参数
Object[] param = {null,"张百万",20,"女",10000,"1990-12-26"};
// 4.执行update方法
Connection con = DruidUtils.getConnection();
int i = qr.update(con, sql, param);
// 5.释放资源
//con.close();
DbUtils.closeQuietly(con);
}
// 修改操作 修改姓名为 张百万的员工的工资为 15000
@Test
public void testUpdate() throws SQLException {
// 1.创建 核心类 自动模式 需要传递 数据库连接池对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "update employee set salary = ? where ename = ?";
// 3.设置占位符参数
Object[] param = {15000,"张百万"};
// 4.执行修改操作 自动模式不需要传入connection对象
qr.update(sql,param);
}
// 删除操作 删除id为1的 记录
@Test
public void testDelete() throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "delete from employee where eid = ?";
// 如果只有一个参数的话 不需要创建数组
qr.update(sql,1);
}
}
QueryRunner实现查询操作
ResultSetHandler接口简介
ResultSetHandler可以对查询出来的ResultSet结果集进行处理,达到一些业务上的需求。
ResultSetHandler 结果集处理类
本例展示的是使用ResultSetHandler接口的几个常见实现类实现数据库的增删改查,可以大大减少代码量,优化程序。
每一种实现类都代表了对查询结果集的一种处理方式
ResultSetHandler 实现类 | 说明 |
---|---|
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
ArrayListHandler | 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中 |
BeanHandler | 将结果集中第一条记录封装到一个指定的javaBean中 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,再将这些javaBean在封装到List集合中 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值 |
MapHandler | 将结果集中第一条记录封装到了Map<String, Object>集合中,key就是字段名称,value就是字段值 |
MapListHandler | 将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中 |
ScalarHandler | 它是用于封装单个数据。例如 select count(*) from 表操作 |
ResultSetHandler 常用实现类测试
QueryRunner的查询方法
query方法的返回值都是泛型,具体的返回值类型,会根据结果集的处理方式发生变化
方法 | 说明 |
---|---|
query(String sql, handler ,Object[] param) | 自动模式创建QueryRunner, 执行查询 |
query(Connection con,String sql,handler,Object[] param) | 手动模式创建QueryRunner, 执行查询 |
public class DBUtilsDemo03 {
/**
* 查询id为5的记录,封装到数组中
* ArrayHandler 将结果集的第一条数据封装到数组中
*/
@Test
public void testFindById() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from employee where eid = ?";
// 3.执行查询操作
Object[] query = qr.query(sql, new ArrayHandler(), 5);
// 4.获取数据
System.out.println(Arrays.toString(query));
}
/**
* 查询所有数据,封装到List集合中
* ArrayListHandler可以将每条数据先封装到数组中,再将数组封装到集合中
*/
@Test
public void testFindAll() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from employee";
// 3.执行查询
List<Object[]> query = qr.query(sql, new ArrayListHandler());
// 4.遍历集合获取数据
for (Object[] objects : query) {
System.out.println(Arrays.toString(objects));
}
}
/**
* 查询id为3的记录,封装到指定JavaBean中
* BeanHandler 将结果集的第一条数据封装到 javaBean中
*/
@Test
public void testFindIdJavaBean() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from employee where eid = ?";
// 3.执行查询
Employee employee = qr.query(sql, new BeanHandler<Employee>(Employee.class), 3);
// 4.打印获取数据
System.out.println(employee);
}
/**
* 查询薪资大于 3000 的所有员工信息,封装到JavaBean中再封装到List集合中
* BeanListHandler 将结果集的每一条和数据封装到 JavaBean中 再将JavaBean 放到list集合中
*/
@Test
public void testFindBySalary() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from employee where salary > ?";
// 3.执行查询
List<Employee> list = qr.query(sql, new BeanListHandler<Employee>(Employee.class), 3000);
// 4.遍历获取数据
for (Employee employee : list) {
System.out.println(employee);
}
}
/**
* 查询姓名是 张百万的员工信息,将结果封装到Map集合中
* MapHandler 将结果集的第一条记录封装到 Map<String,Object>中
* key对应的是 列名 value对应的是 列的值
*/
@Test
public void testFindByName() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from employee where ename = ?";
// 3.执行查询
Map<String, Object> map = qr.query(sql, new MapHandler(), "张百万");
// 4.遍历获取数据
Set<Map.Entry<String, Object>> entries = map.entrySet();
for (Map.Entry<String,Object> entry : entries) {
// 打印结果
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
/**
* 查询所有员工的薪资总额
* ScalarHandler 用于封装单个的数据
*/
@Test
public void testSum() throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select sum(salary) from employee";
// 3.执行查询
Double sum = (Double)qr.query(sql,new ScalarHandler<>());
// 获取数据
System.out.println("元薪资总额:" + sum);
}
}
数据库批处理
什么是批处理
1、批处理(batch) 操作数据库
批处理指的是一次操作中执行多条SQL语句,批处理相比于一次一次执行效率会提高很多。
当向数据库中添加大量的数据时,需要用到批处理。
2、举例: 送货员的工作:
未使用批处理的时候,送货员每次只能运送 一件货物给商家;
使用批处理,则是送货员将所有要运送的货物, 都用车带到发放处派给客户。
实现批处理
Statement和PreparedStatement都支持批处理操作,这里我们介绍一下PreparedStatement的批处理方式:
1、用到的方法
方法 | 说明 |
---|---|
void addBatch() | 将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中,通过调用方法 executeBatch 可以批量执行此列表中的命令 |
int[] executeBatch() | 每次提交一批命令到数据库中执行,如果所有的命令都成功执行了,那么返回一个数组,这个数组是说明每条命令所影响的行数 |
2、mysql 批处理是默认关闭的,所以需要加一个参数才打开mysql 数据库批处理,在url中添加
rewriteBatchedStatements=true
例如: url=jdbc:mysql://127.0.0.1:3306/db5?characterEncoding=UTF-8&rewriteBatchedStatements=true
3、测试
-- 创建一张表
CREATE TABLE testBatch (
id INT PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(50)
)
public class BatchInsert {
// 使用批处理 向表中添加 10000条数据
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection con = DruidUtils.getConnection();
// 2.获取预处理对象
PreparedStatement ps = con.prepareStatement("insert into testbatch(uname) values(?)");
// 3.执行批量插入操作
for (int i = 0; i < 10000; i++) {
ps.setString(1,"小强" + i);
// 将SQL添加到批处理表里
ps.addBatch();
}
// 添加时间戳,测试执行效率
long start = System.currentTimeMillis();
// 4.统一执行批量插入操作
ps.executeBatch();
long end = System.currentTimeMillis();
System.out.println("插入10000条数据需要使用:" + (end - start) + "毫秒!");
// 5.关闭连接
DruidUtils.close(con,ps);
}
}
MySql元数据
元数据概念
除了表之外的数据都是元数据,可以分为三类
查询结果信息: UPDATE 或 DELETE语句 受影响的记录数。
数据库和数据表的信息: 包含了数据库及数据表的结构信息。
MySQL服务器信息: 包含了数据库服务器的当前状态,版本号等。
常用命令
-- 元数据相关的命令介绍
-- 1.查看服务器当前状态
show status;
-- 2.查看MySQl的版本信息
select version();
-- 3.查询表中的详细信息,和desc table_name一样
show columns from table_name;
-- 4.显示数据表的详细索引信息,包括PRIMARY KEY(主键)
show index from table_name;
-- 5.列出所有数据库
show databases;
-- 6.显示当前数据库的所有表
show tables;
-- 7.获取当前的数据库名
select database();
使用JDBC 获取元数据
通过JDBC 也可以获取到元数据,比如数据库的相关信息,或者当我们使用程序查询一个不熟悉的表时,我们可以通过获取元素据信息,了解表中有多少个字段,字段的名称 和 字段的类型
常用类介绍
1、JDBC中描述元数据的类
元数据类 | 作用 |
---|---|
DatabaseMetaData | 描述数据库的元数据对象 |
ResultSetMetaData | 描述结果集的元数据对象 |
2、获取元数据对象的方法 : getMetaData ()
connection 连接对象, 调用 getMetaData () 方法,获取的是DatabaseMetaData 数据库元数据对象
PrepareStatement 预处理对象调用 getMetaData () , 获取的是ResultSetMetaData , 结果集元数据对象
3、DatabaseMetaData的常用方法
方法说明 |
---|
getURL() : 获取数据库的URL |
getUserName(): 获取当前数据库的用户名 |
getDatabaseProductName(): 获取数据库的产品名称 |
getDatabaseProductVersion(): 获取数据的版本号 |
getDriverName(): 返回驱动程序的名称 |
isReadOnly(): 判断数据库是否只允许只读 true 代表只读 |
4、ResultSetMetaData的常用方法
方法声明 |
---|
getColumnCount() : 当前结果集共有多少列 |
getColumnName(int i) : 获取指定列号的列名,参数是整数 从1开始 |
getColumnTypeName(int i): 获取指定列号列的类型,参数是整数 从1开始 |
public class TestMetaData {
// 1.获取数据库相关的元数据信息 使用DatabaseMetaData
@Test
public void testDataBaseMetaData() throws SQLException {
// 1.获取数据库连接对象 connection
Connection connection = DruidUtils.getConnection();
// 2.获取代表数据库的 元数据对象 DatabaseMetaData
DatabaseMetaData metaData = connection.getMetaData();
// 3.获取数据库相关元数据信息
String url = metaData.getURL();
System.out.println("数据库url:" + url);
String userName = metaData.getUserName();
System.out.println("当前用户:" + userName);
String productName = metaData.getDatabaseProductName();
System.out.println("数据库产品名:" + productName);
String version = metaData.getDatabaseProductVersion();
System.out.println("数据库版本:" + version);
String driverName = metaData.getDriverName();
System.out.println("驱动名称:" + driverName);
// 判断当前数据库是否只允许只读
boolean b = metaData.isReadOnly(); // 如果是true 表示只读
if (b){
System.out.println("当前数据库只允许读操作!");
}else {
System.out.println("不是只读数据库!");
}
// 释放资源
connection.close();
}
// 获取结果集的元数据信息
@Test
public void testResultSetMetaData() throws SQLException {
// 1.获取连接
Connection con = DruidUtils.getConnection();
// 2.获取预处理对象
PreparedStatement ps = con.prepareStatement("select * from employee");
ResultSet resultSet = ps.executeQuery();
// 3.获取结果集元数据对象
ResultSetMetaData metaData = ps.getMetaData();
// 1.获取当前结果集共有多少列
int count = metaData.getColumnCount();
System.out.println("当前结果集中共有:" + count + "列");
// 2.获取结果集中列的名称和类型
for (int i = 1; i < count; i++) {
String columnName = metaData.getColumnName(i);
System.out.println("列名:" + columnName);
String columnTypeName = metaData.getColumnTypeName(i);
System.out.println("类型:" + columnTypeName);
}
// 释放资源
DruidUtils.close(con,ps,resultSet);
}
}
XML
XML基本介绍
概述
XML即可扩展标记语言(Extensible Markup Language)
W3C在1998年2月发布1.0版本,2004年2月又发布1.1版本,但因为1.1版本不能向下兼容1.0版本,所以1.1没有人用。同时,在2004年2月W3C又发布了1.0版本的第三版。我们要学习的还是1.0版本 !
特点:
可扩展的,标签都是自定义的
语法十分严格
XML的作用
功能 | 说明 |
---|---|
存储数据 | 我们通常在数据库中存储数据。如果希望数据的可移植性更强,我们可以把数据存储 XML 文件中 |
配置文件 | 作为各种技术框架的配置文件使用 (最多) |
在网络中传输 | 客户端可以使用XML格式向服务器端发送数据,服务器接收到xml格式数据,进行解析 |
XML的语法
XML文档声明格式
文档声明必须为结束;
文档声明必写在第一行;
1、语法格式:
<?xml version="1.0" encoding="UTF-8"?>
2、属性说明:
**versioin:**指定XML文档版本。必须属性,因为我们不会选择1.1,只会选择1.0;
**encoding:**指定当前文档的编码。可选属性,默认值是utf-8;
元素
Element 元素:是XML文档中最重要的组成部分
1、元素的命名规则
1.不能使用空格,不能使用冒号
2.xml 标签名称区分大小写
3.XML 必须有且只有一个根元素
2、语法格式:
<users><users>
a.XML 必须有且只有一个根元素,它是所有其他元素的父元素,比如以下实例中 users 就是根元素:
<?xml version="1.0" encoding="utf-8" ?>
<users>
</users>
b.普通元素的结构开始标签、元素体、结束标签组成
<hello> 大家好 </hello>
c.元素体:元素体可以是元素,也可以是文本
<hello>
<a>你好</a>
</hello>
d.空元素:空元素只有开始标签,而没有结束标签,但元素必须自己闭合
<close/>
属性
<bean id="" class=""> </bean>
1.属性是元素的一部分,它必须出现在元素的开始标签中
2.属性的定义格式:属性名=属性值,其中属性值必须使用单引或双引
3.一个元素可以有0~N个属性,但一个元素中不能出现同名属性
4.属性名不能使用空格、冒号等特殊字符,且必须以字母开头
注释
XML的注释,以“ ”结束。注释内容会被XML解析器忽略!
<?xml version="1.0" encoding="UTF-8" ?>
<users>
<user id ="123">
<name>张百万</name>
<age>15</age>
</user>
<user id = "456">
<name>小斌</name>
<age>18</age>
<hobby>
<pingpang>
</pingpang>
</hobby>
</user>
<!-- 空元素没有结束标签 -->
<close/>
</users>
<!-- XML的注释
1.XML中必须进行文档声明
version 版本信息
encoding 编码
2.XML中的文档声明必须写在第一行
3.XML中的元素标签 命名规则
1.标签定义 不能使用空格或者冒号
2.xml的名称 区分大小写
4.XML中有且只有一个根元素
5.元素体可以是文本或者还是标签
6.属性是元素的一部分,只能出现在元素的开始标签中
属性值必须使用单引号或者双引号包裹
一个元素标签可以定义多个属性
-->
使用XML 描述数据表中的数据
<?xml version="1.0" encoding="UTF-8" ?>
<employees> <!-- 根元素 -->
<employee eid="2"> <!-- employee元素1 eid属性 -->
<ename>林黛玉</ename> <!-- 元素体 -->
<age>20</age>
<sex>女</sex>
<salary>5000</salary>
<empdate>2010-01-01</empdate>
</employee>
<employee eid="3">
<ename>杜甫</ename>
<age>40</age>
<sex>男</sex>
<salary>15000</salary>
<empdate>2010-01-01</empdate>
</employee>
</employees>
XML约束
在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
常见的xml约束:
DTD
Schema
作为程序员只要掌握两点
会阅读
会引入
不用自己编写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVsX8KHu-1621589561617)(E:\MarkDown\拉勾笔记\XML约束)]
DTD约束
DTD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。
编写DTD
开发中,我们不会自己编写DTD约束文档
通常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。常见框架使用DTD约束有:Struts2、hibernate等
创建Student.dtd约束文件
<!ELEMENT students (student+) >
<!ELEMENT student (name,age,sex)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
<!ATTLIST student number ID #REQUIRED>
<!--
Element 定义元素
students (student+):students 代表根元素
student+:代表根标签中 至少有一个 student子元素
student (name,age,sex):student标签中可以 包含的子元素 按顺序出现
#PCDATA:表示普通文本内容
ATTLIST:用来定义属性
student number ID:student标签中 有一个ID属性 叫做number
#REQUIRED:表示number属性必须填写
ID 唯一的值 不能重复 值只能是字母或者下划线开头
-->
引入DTD
引入dtd文档到xml文档中,两种方式
1、内部dtd:将约束规则定义在xml文档中
2、外部dtd:将约束的规则定义在外部的dtd文件中
本地:
网络:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE students SYSTEM "student.dtd">
<students>
<student number="s1">
<name>长海</name>
<age>20</age>
<sex>男</sex>
</student>
<student number="s2">
<name>大玲子</name>
<age>18</age>
<sex>女</sex>
</student>
</students>
Schema约束
什么是Schema
1.Schema是新的XML文档约束,比DTD强大很多,是DTD 替代者;
2.Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。
3.Schema 功能更强大,内置多种简单和复杂的数据类型
4.Schema 支持命名空间 (一个XML中可以引入多个约束文档)
Schema约束示例
student.xsd文件
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.lagou.com/xml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lagou.com/xml" elementFormDefault="qualified">
<xsd:element name="students" type="studentsType"/>
<xsd:complexType name="studentsType">
<xsd:sequence>
<xsd:element name="student" type="studentType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="studentType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="age" type="ageType" />
<xsd:element name="sex" type="sexType" />
</xsd:sequence>
<xsd:attribute name="number" type="numberType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="sexType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="male"/>
<xsd:enumeration value="female"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ageType">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0"/>
<xsd:maxInclusive value="200"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="numberType">
<xsd:restriction base="xsd:string">
<xsd:pattern value="hehe_\d{4}"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
Xml Schema的根元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NDua1c11-1621589561619)(E:\MarkDown\拉勾笔记\Xml Schema的根元素)]
XML引入Schema约束
xml中引入schema约束的步骤:
1.查看schema文档,找到根元素,在xml中写出来
2.根元素来自哪个命名空间。使用xmlns指令来声明
3.引入 w3c的标准命名空间,复制即可
4.引入的命名空间跟哪个xsd文件对应?
使用schemaLocation来指定:两个取值:第一个为命名空间 第二个为xsd文件的路径
5.命名空间
6.student.xml
<?xml version="1.0" encoding="UTF-8" ?>
<students
xmlns="http://www.wei.com/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.wei.com/xml student.xsd"
>
<student number="hehe_1234">
<name>张三</name>
<age>20</age>
<sex>male</sex>
</student>
<student number="hehe_4567">
<name>张三</name>
<age>200</age>
<sex>male</sex>
</student>
</students>
XML 解析
解析概述
当将数据存储在XML后,我们就希望通过程序获得XML的内容。如果我们使用Java基础所学习的IO知识是可以完成的,不过你需要非常繁琐的操作才可以完成,且开发中会遇到不同问题(只读、读写)。人们为不同问题提供不同的解析方式,并提交对应的解析器,方便开发人员操作XML。
XML解析方式
开发中比较常见的解析方式有两种,如下:
DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象。
优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
缺点:XML文档过大,可能出现内存溢出显现。
SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方
式进行具体解析,每执行一行,都将触发对应的事件。(了解)
优点:占用内存少 处理速度快,可以处理大文件
缺点:只能读,逐行后将释放资源。
XML常见的解析器
解析器:就是根据不同的解析方式提供的具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包。
JAXP:sun公司提供的解析器,支持DOM和SAX两种思想
**DOM4J:**一款非常优秀的解析器 , Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。
它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP。
Jsoup:jsoup 是一款Java 的HTML解析器,也可以解析XML
PULL:Android内置的XML解析方式,类似SAX。
dom4j 的使用
导入JAR包
API介绍
使用核心类SaxReader加载xml文档获得Document,通过Document 对象获得文档的根元素,然后就可以操作了。
常用API:
SaxReader对象
read(…) 加载执行xml文档
Document对象
getRootElement() 获得根元素
Element对象
elements(…) 获得指定名称的所有子元素。可以不指定名称
element(…) 获得指定名称的第一个子元素。可以不指定名称
getName() 获得当前元素的元素名
attributeValue(…) 获得指定属性名的属性值
elementText(…) 获得指定名称子元素的文本值
getText() 获得当前元素的文本内容
准备xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<users xmlns="http://www.wei.com/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.wei.com/xml user.xsd"
>
<user id="001">
<name>张百万</name>
<age>25</age>
<hobby>抽烟</hobby>
</user>
<user id="002">
<name>于谦</name>
<age>55</age>
<hobby>烫头</hobby>
</user>
<user id="003">
<name>小斌</name>
<age>25</age>
<hobby>喝酒</hobby>
</user>
</users>
读取XML
public class TestDOM4j {
// 获取XML文件中的 所有元素(标签)
@Test
public void test1() throws DocumentException {
// 1.获取XML解析对象
SAXReader reader = new SAXReader();
// 2.解析XML 获取文档对象 document
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml03\\user.xml");
// 3.获取根元素
Element rootElement = document.getRootElement();
// 获取根元素的名称
System.out.println(rootElement.getName());
List<Element> elements = rootElement.elements();
// 获取根元素下的标签
for (Element element : elements) {
System.out.println("根标签下的子节点:" + element.getName());
List<Element> elist = element.elements();
for (Element e : elist) {
System.out.println("user标签下的子节点:" + e.getName());
}
break;
}
}
// 获取XML中标签的文本信息 和 属性信息
@Test
public void test2() throws DocumentException {
// 1.获取解析XML的SAXReader
SAXReader reader = new SAXReader();
// 2.获取文档对象
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml03\\user.xml");
// 3.获取根节点
Element rootElement = document.getRootElement();
// 4.获取子节点
List<Element> elements = rootElement.elements();
// 5.获取集合中的第一个子节点
Element user = elements.get(0);
// 6.获取节点中的文本信息
String id = user.attributeValue("id"); // 获取属性id的值
String name = user.elementText("name");
String age = user.elementText("age");
String hobby = user.element("hobby").getText();
//打印
System.out.println(id + " " + name +" " + age + " " + hobby);
}
}
xpath方式读取xml
xpath介绍
XPath 是一门在 XML 文档中查找信息的语言。 可以是使用xpath查找xml中的内容。
XPath 的好处
由于DOM4J在解析XML时只能一层一层解析,所以当XML文件层数过多时使用会很不方便,结合XPATH就可以直接获取到某个元素
XPath基本语法介绍
使用dom4j支持xpath的操作的几种主要形式
语法 | 说明 |
---|---|
/AAA/DDD/BBB | 表示一层一层的,AAA下面 DDD下面的BBB |
//BBB | 表示和这个名称相同,表示只要名称是BBB,都得到 |
//* | 所有元素 |
BBB[1] , BBB[last()] | 第一种表示第一个BBB元素,第二种表示最后一个BBB元素 |
//BBB[@id] | 表示只要BBB元素上面有id属性,都得到 |
//BBB[@id=‘b1’] | 表示元素名称是BBB,在BBB上面有id属性,并且id的属性值是b1 |
API介绍
selectSingleNode(query): 查找和 XPath 查询匹配的一个节点。
参数是Xpath 查询串。
selectNodes(query): 得到的是xml根节点下的所有满足 xpath 的节点;
参数是Xpath 查询串。
Node: 节点对象
Xpath读取XML
准备数据:book.xml
<?xml version="1.0" encoding="UTF-8" ?>
<bookstore>
<book id="book1">
<name>金瓶梅</name>
<author>金圣叹</author>
<price>99</price>
</book>
<book id="book2">
<name>红楼梦</name>
<author>曹雪芹</author>
<price>69</price>
</book>
<book id="book3">
<name>Java编程思想</name>
<author>埃克尔</author>
<price>59</price>
</book>
</bookstore>
解析xml
public class TestXPath {
/**
* 使用selectSingleNOde()方法 查询指定的节点信息
*/
@Test
public void test1() throws DocumentException {
// 1.创建XML解析对象
SAXReader reader = new SAXReader();
// 2.解析XML 获取文档对象
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml04\\book.xml");
// 3.通过selectSingleNOde()方法 获取name节点
Node node1 = document.selectSingleNode("/bookstore/book/name");
System.out.println("节点的名称:" + node1.getName());
System.out.println("书名:" + node1.getText());
// 4.获取第二本书的书名
Node node2 = document.selectSingleNode("/bookstore/book[2]/name");
System.out.println("书名:" + node2.getText());
}
/**
* 使用selectSingleNode()方法 获取属性值 或 通过属性值获取到节点信息
*/
@Test
public void test2() throws DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml04\\book.xml");
// 1.获取第一个book节点中的 id属性值
Node node1 = document.selectSingleNode("/bookstore/book/attribute::id");
System.out.println("第一个book的id属性值:" + node1.getText());
// 2.获取最后衣蛾book节点的 id属性值
Node node2 = document.selectSingleNode("/bookstore/book[last()]/attribute::id");
System.out.println("最后一个book节点中的属性值:" + node2.getText());
// 3.通过id的值 获取book2节点中的书名
Node node3 = document.selectSingleNode("/bookstore/book[@id='book2']");
String name = node3.selectSingleNode("name").getText();
System.out.println("id为book2的结点的书名是:" + name);
}
/**
* 使用 selectNodes()方法 获取对应名称的所有节点
*/
@Test
public void test3() throws DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml04\\book.xml");
// 1.查询所有结点
List<Node> list = document.selectNodes("//*");
for (Node node : list) {
System.out.println("节点名:" + node.getName());
}
// 2.获取所有的书名
List<Node> list1 = document.selectNodes("//name");
for (Node node : list1) {
System.out.println("书名:" + node.getText());
}
// 3.获取id值为book1的节点中的所有内容
List<Node> list2 = document.selectNodes("/bookstore/book[@id='book1']//*");
for (Node node : list2) {
System.out.println(node.getName() + " = " + node.getText());
}
}
}
JDBC自定义XML
定义配置文件
创建自定义xml 文件,保存 数据库连接信息
jdbc-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<jdbc>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db5?characterEncoding=UTF-8</property>
<property name="user">root</property>
<property name="password">123456</property>
</jdbc>
编写工具类(配置式)
public class JDBCUtils {
//1.定义字符串变量 保存连接信息
public static String DRIVERNAME;
public static String URL;
public static String USER;
public static String PASSWORD;
//2.静态代码块
static{
//使用 XPath语法 对xml中的数据进行读取
SAXReader reader = new SAXReader();
try {
Document document = reader.read("E:\\jdbc_work\\xml_task03\\src\\com\\wei\\xml05\\jdbc_config.xml");
//1.获取驱动名称
Node driver = document.selectSingleNode("/jdbc/property[@name='driverClass']");
DRIVERNAME = driver.getText();
//2.获取URL
Node url = document.selectSingleNode("/jdbc/property[@name='jdbcUrl']");
URL = url.getText();
//3.获取用户名
Node user = document.selectSingleNode("/jdbc/property[@name='user']");
USER = user.getText();
//4.获取密码
Node password = document.selectSingleNode("/jdbc/property[@name='password']");
PASSWORD = password.getText();
//注册驱动
Class.forName(DRIVERNAME);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
测试工具类
public class TestJDBC {
// 查询所有员工信息
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection connection = JDBCUtils.getConnection();
// 2.获取Statement对象 执行SQL
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from employee");
// 3.处理结果集
while(resultSet.next()){
String name = resultSet.getString("ename");
System.out.println("员工的姓名:" + name);
}
// 4.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
综合案例
商城案例表设计
创建数据库db6,用户表user、订单表orders,商品分类表category,商品表product,订单项表(中间表)orderitem
CREATE DATABASE db6 CHARACTER SET utf8;
-- 用户表
CREATE TABLE USER (
uid VARCHAR(32) PRIMARY KEY, -- 用户ID
username VARCHAR(20), -- 用户名
PASSWORD VARCHAR(20), -- 密码
telephone VARCHAR(20), -- 电话
birthday DATE, -- 生日
sex VARCHAR(10) -- 性别
);
INSERT INTO USER VALUES
('001','渣渣辉','123456','13511112222','2015-11-04','男'),
('002','药水哥','123456','13533334444','1990-02-01','男'),
('003','大明白','123456','13544445555','2015-11-03','男'),
('004','长海','123456','13566667777','2000-02-01','男'),
('005','乔杉','123456','13588889999','2000-02-01','男');
-- 订单表
CREATE TABLE orders (
oid VARCHAR(32) PRIMARY KEY, -- 订单id
ordertime DATETIME, -- 下单时间
total DOUBLE, -- 总金额
NAME VARCHAR(20), -- 收货人姓名
telephone VARCHAR(20), -- 电话
address VARCHAR(30), -- 地址
state INT(11), -- 订单状态
uid VARCHAR(32), -- 外键字段 对应用户表id
CONSTRAINT ofk_0001 FOREIGN KEY (uid) REFERENCES USER (uid)
);
-- 插入一条订单数据
INSERT INTO orders
VALUES('order001','2019-10-11',5500,'乔杉','15512342345','皇家洗浴',0,'001');
-- 商品分类表
CREATE TABLE category (
cid VARCHAR(32) PRIMARY KEY,
cname VARCHAR(20)
);
INSERT INTO `category` VALUES ('1','手机数码'),('2','电脑办公'),('3','运动鞋服'),('4','图书音像');
-- 商品表
CREATE TABLE product (
pid VARCHAR(32) PRIMARY KEY, -- 商品id
pname VARCHAR(50), -- 商品名称
price DOUBLE, -- 商品价格
pdesc VARCHAR(255), -- 商品描述
pflag INT(11), -- 商品状态 1 上架 ,0 下架
cid VARCHAR(32), -- 外键对应 分类表id
KEY sfk_0001 (cid),
CONSTRAINT sfk_0001 FOREIGN KEY (cid) REFERENCES category (cid)
);
INSERT INTO `product` VALUES
('1','小米6',2200,'小米 移动联通电信4G手机 双卡双待',0,'1'),
('2','华为Mate9',2599,'华为 双卡双待 高清大屏',0,'1'),
('3','OPPO11',3000,'移动联通 双4G手机',0,'1'),
('4','华为荣耀',1499,'3GB内存标准版 黑色 移动4G手机',0,'1'),
('5','华硕台式电脑',5000,'爆款直降,满千减百',0,'2'),
('6','MacBook',6688,'128GB 闪存',0,'2'),
('7','ThinkPad',4199,'轻薄系列1)',0,'2'),
('8','联想小新',4499,'14英寸超薄笔记本电脑',0,'2'),
('9','李宁音速6',500,'实战篮球鞋',0,'3'),
('10','AJ11',3300,'乔丹实战系列',0,'3'),
('11','AJ1',5800,'精神小伙系列',0,'3');
-- 订单项表 (中间表)
CREATE TABLE orderitem (
itemid VARCHAR(32) PRIMARY KEY, -- 订单项ID
pid VARCHAR(32), -- 外键 对应商品表 id
oid VARCHAR(32), -- 外键 对应订单表 id
KEY fk_0001 (pid),
KEY fk_0002 (oid),
CONSTRAINT fk_0001 FOREIGN KEY (pid) REFERENCES product (pid),
CONSTRAINT fk_0002 FOREIGN KEY (oid) REFERENCES orders (oid)
);
-- 向中间表中插入两条数据
INSERT INTO orderitem VALUES('item001','1','order001');
INSERT INTO orderitem VALUES('item002','11','order001');
环境搭建
项目结构
com.wei.app 测试包 用于对DAO代码进行测试
com.wei.dao dao包 数据访问层,包含所有对数据库的相关操作的类
com.wei.entity 实体包 保存根据数据库表 对应创建的JavaBean类
com.wei.utils 工具包
导入所需Jar包
导入myjar仓库到项目中就可以了。
导入配置文件及工具类
JavaBean类创建
设计用户与订单
User类
public class User {
private String uid;
private String username;
private String password;
private String telephone;
private String birthday;
private String sex;
public User() {
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", telephone='" + telephone + '\'' +
", birthday='" + birthday + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
Orders类
public class Orders {
private String oid; // 订单ID
private String ordertime; // 下单时间
private double total; // 订单总金额
private String name; // 收货人姓名
private String telephone; // 收货人电话
private String address; // 收货人地址
private int state; // 订单状态 1代表已支付,0代表未支付
private String uid; // 外键 uid
private User user; // 保存订单对应的用户的详细信息
// 描述 多对一关系 一个订单中包含多个订单项信息
List<OrderItem> list = new ArrayList<>();
public Orders() {
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public String getOrdertime() {
return ordertime;
}
public void setOrdertime(String ordertime) {
this.ordertime = ordertime;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Orders{" +
"oid='" + oid + '\'' +
", ordertime='" + ordertime + '\'' +
", total=" + total +
", name='" + name + '\'' +
", telephone='" + telephone + '\'' +
", address='" + address + '\'' +
", state=" + state +
", uid='" + uid + '\'' +
'}';
}
}
设计商品与分类
Category类
/**
* @auther weiwei
* @date 2021/5/20 14:53
* @description 商品分类表对应的java类
*/
public class Category {
private String cid;
private String cname;
public Category() {
}
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Category{" +
"cid='" + cid + '\'' +
", cname='" + cname + '\'' +
'}';
}
}
Product类
public class Product {
private String pid;
private String pname;
private double price;
private String pdesc;
private int pflag; // 是否上架 1 上架,0 下架
private String cid; // 外键
private Category category; // 保存分类的详细信息
public Product() {
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getPdesc() {
return pdesc;
}
public void setPdesc(String pdesc) {
this.pdesc = pdesc;
}
public int getPflag() {
return pflag;
}
public void setPflag(int pflag) {
this.pflag = pflag;
}
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
@Override
public String toString() {
return "Product{" +
"pid='" + pid + '\'' +
", pname='" + pname + '\'' +
", price=" + price +
", pdesc='" + pdesc + '\'' +
", pfla=" + pflag +
", cid='" + cid + '\'' +
'}';
}
}
OderItem类
public class OrderItem {
private String itemid; // 订单项ID
private String pid; // 外键 指向了商品表的主键
private String oid; // 外键 指向了订单表的主键
private Product product; // 订单项中商品详细信息
private Orders orders; // 订单项所属的订单的详细信息
public OrderItem() {
}
public String getItemid() {
return itemid;
}
public void setItemid(String itemid) {
this.itemid = itemid;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Orders getOrders() {
return orders;
}
public void setOrders(Orders orders) {
this.orders = orders;
}
@Override
public String toString() {
return "OrderItem{" +
"itemid='" + itemid + '\'' +
", pid='" + pid + '\'' +
", oid='" + oid + '\'' +
'}';
}
}
编写DAO类
UserDao
public class UserDao {
// 需求一: 编写一个注册用户的方法,接收的参数是一个User对象
public int register(User user) throws SQLException {
// 1.获取QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.dataSource);
// 2.编写SQL
String sql = "insert into user values(?,?,?,?,?,?)";
Object[] param = {user.getUid(),user.getUsername(),user.getPassword(),user.getTelephone()
,user.getBirthday(),user.getSex()};
// 3.执行插入操作
int update = qr.update(sql,param);
return update;
}
// 需求二: 编写一个用户登录的方法,接收的参数是 用户名 和密码,返回值是User对象
public User login(String username, String password) throws SQLException {
// 1.获取QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.dataSource);
// 2.编写SQL
String sql = "select * from user where username = ? and password = ?";
// 3.执行查询 使用BeanHandler来封装结果集,获取结果集中的第一条数据
User user = qr.query(sql, new BeanHandler<User>(User.class), username, password);
return user;
}
}
public class TestUserDao {
UserDao userDao = new UserDao();
// 1.测试 注册用户
@Test
public void testRegister() throws SQLException {
// 1.创建User
User user = new User();
user.setUid(UUIDUtils.getUUID());
user.setUsername("武松");
user.setPassword("123456");
user.setTelephone("13512341234");
user.setSex("男");
user.setBirthday(DateUtils.getDateFormart());
// 2.执行注册
int register = userDao.register(user);
if (register > 0){
System.out.println("注册成功,欢迎您:" + user.getUsername());
}else {
System.out.println("注册失败!!");
}
}
// 2.用户登陆测试
@Test
public void testLogin() throws SQLException {
// 1.调用UserDao的login方法 传入用户名和密码
User user = userDao.login("大郎", "123456");
// 2.判断user是否为空
if (user != null){
System.out.println("欢迎您!" + user.getUsername());
}else {
System.out.println("用户名或者密码错误!登良失败!");
}
}
}
ProductDao
public class ProductDao {
// 需求1: 根据商品ID 获取商品名称 ,商品价格以及商品所属分类的名称
public Product findProductById(String pid) throws SQLException {
// 1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 2.编写SQL
String sql = "select * from product where pid = ?";
// 3.执行查询
Product product = qr.query(sql, new BeanHandler<Product>(Product.class), pid);
// 4.获取外键的值 对用分类的ID
String cid = product.getCid();
// 商品对应的 分类信息
Category category = findCategoryById(cid);
product.setCategory(category);
return product;
}
// 需求2: 根据分类ID查询分类信息
public Category findCategoryById(String cid) throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from category where cid = ?";
Category category = qr.query(sql, new BeanHandler<Category>(Category.class), cid);
return category;
}
// 需求3: 查询指定分类ID 下的商品个数
public int getCount(String cid) throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select count(*) from product where cid = ?";
// 获取单列数据 使用ScalarHandler 来封装
Long count = qr.query(sql, new ScalarHandler<>(), cid);
// 将Long类型的包装类 转换为int类型并返回
return count.intValue();
}
// 需求4: 查询指定分类ID 下的所有商品信息
public List<Product> findProductByCid(String cid) throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from product where cid = ?";
// 查询结果是一个list集合,使用BeanListHandler来封装
List<Product> list = qr.query(sql, new BeanListHandler<Product>(Product.class), cid);
return list;
}
}
public class TestProductDao {
ProductDao productDao = new ProductDao();
// 测试 根据商品ID 获取商品名称 ,商品价格以及商品所属分类的名称
@Test
public void testFindProductById() throws SQLException {
// 1.调用方法获取商品对象
Product product = productDao.findProductById("1");
// 2.打印信息
System.out.println(product.getPname() + " " + product.getPrice() + " " + product.getCategory().getCname());
}
// 测试 查询指定分类ID 下的商品个数
@Test
public void testgetCount() throws SQLException {
// 查询分类id为3 的分类下 有几个商品
int count = productDao.getCount("3");
System.out.println("分类id为3的商品个数是:" + count);
}
// 测试 查询指定分类ID 下的所有商品信息
@Test
public void testfindProductByCid() throws SQLException {
// 查询分类id为2 的所有商品信息
List<Product> list = productDao.findProductByCid("2");
for (Product product : list) {
System.out.println(product);
}
}
}
OrdersDao
public class OrdersDao {
// 需求1: 获取 uid为 001 的用户的所有订单信息
public List<Orders> fingAllOrders(String uid) throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from orders where uid = ?";
// 一个用户所有的订单信息
List<Orders> ordersList = qr.query(sql, new BeanListHandler<Orders>(Orders.class), uid);
return ordersList;
}
// 需求2: 获取订单编号为 order001的订单中的所有商品信息
public List<Product> findOrderById(String oid) throws SQLException {
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 1.查询订单项表 获取订单项表中 订单ID为order001 的数据
String sql = "SELECT pid FROM orderitem WHERE oid = ?";
// 2.查询的结果是 多条订单项数据
List<OrderItem> list = qr.query(sql, new BeanListHandler<OrderItem>(OrderItem.class), oid);
// 3.创建集合保存商品信息
List<Product> productList = new ArrayList<>();
ProductDao productDao = new ProductDao();
// 4.遍历订单项集合 获取pid
for (OrderItem orderItem : list) {
// 4.1从orderitem中获取pid
String pid = orderItem.getPid();
// 4.2调用productDao
Product product = productDao.findProductById(pid);
// 4.3保存到集合
productList.add(product);
}
// 返回 订单中对应的商品信息
return productList;
}
}
public class TestOrdersDao {
OrdersDao ordersDao = new OrdersDao();
// 测试 获取 uid为 001 的用户的所有订单信息
@Test
public void testFindAllOrders() throws SQLException {
List<Orders> allOrders = ordersDao.fingAllOrders("001");
for (Orders orders : allOrders) {
System.out.println(orders);
}
}
// 测试 获取订单编号为 order001的订单中的所有商品信息
@Test
public void testFindOrderById() throws SQLException {
List<Product> order001 = ordersDao.findOrderById("order001");
for (Product product : order001) {
System.out.println(product);
}
}
}