JDBC概述
数据的持久化
- 持久化(persistence):将内存中的数据保存到可永久保存的存储设备中(如磁盘)。
- 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
什么是 JDBC
- JDBC(Java DataBase Connectivity)java 数据库连接
- 是 JavaEE 平台下的技术规范
- 定义了在 Java 语言中连接数据库,执行 SQL 语句的标准 API
- 可以为多种关系数据库提供统一访问
什么是数据库驱动程序
- 数据库驱动就是直接操作数据库的一个程序
- 不同数据产品的数据库驱动名字有差异
- 在程序中需要依赖数据库驱动来完成对数据库的操作
Java中访问数据库技术
- 基于JDBC标准访问数据库
- 使用第三方ORM 框架,如Hibernate, Mybatis 等访问数据库
程序操作数据库流程
JBDC中常用的类与接口
Driver 接口
Driver 接口的作用是来定义数据库驱动对象应该具备的一些能力。比如与数据库建立连接的方法的定义,该接口是提供给数据库厂商使用的,所有支持java 语言连接的数据库都实现了该接口,实现该接口的类我们称之为数据库驱动类。
DriverManager 类
DriverManager是驱动程序管理器,是负责管理数据库驱动程序的。驱动注册以后,会保存在DriverManager中的已注册列表中。 DriverManager 通过实例化的数据库驱动对象,能够建立应用程序与数据库之间建立连 接。并返回 Connection 接口类型的数据库连接对象。
- getConnection(String jdbcUrl, String user, String password)
该方法通过访问数据库的 url、用户以及密码,返回对应的数据库的 Connection 对象。 - JDBC URL
与数据库连接时,用来连接到指定数据库标识符。在 URL 中包括了该数据库的类型、 地址、端口、库名称等信息。不同品牌数据库的连接 URL 不同。-
连接 MySql 数据库:
Connection conn =DriverManager.getConnection("jdbc:mysql://host:port/database", "user","password");
-
连接 Oracle 数据库:
Connection conn =DriverManager.getConnection("jdbc:oracle:thin:@host:port:database","user","password");
-
Connection 接口
Connection 是数据库的连接(会话)对象。对数据库的一切操作都是在这个连接基础之上进行的,我们可以通过该对象执行sql 语句并返回结果。
常用方法
- createStatement()
创建向数据库发送 sql 的 Statement 接口类型的对象。 - preparedStatement(sql)
创建向数据库发送预编译 sql 的 PrepareSatement 接口类型的对象。 - setAutoCommit(boolean autoCommit)
设置事务是否自动提交。 - commit()
在链接上提交事务。 - rollback()
在此链接上回滚事务。
Statement 接口
用于执行静态 SQL 语句并返回它所生成结果的对象。 由createStatement 创建,用于发送简单的 SQL 语句(不支持动态绑定)。
常用方法
- execute(String sql)
执行参数中的 SQL,返回是否有结果集。 - executeQuery(String sql)
运行 select 语句,返回 ResultSet 结果集。 - executeUpdate(String sql)
运行 insert/update/delete 操作,返回更新的行数。 - addBatch(String sql)
把多条 sql 语句放到一个批处理中。 - executeBatch()
向数据库发送一批 sql 语句执行。
PreparedStatement接口
继承自 Statement 接口,由 preparedStatement 创建,用于发送含有一个或多个参数的 SQL 语句。PreparedStatement 对象比 Statement 对象的效率更高,由于实现了动态的参数绑定,所以可以防止 SQL 注入,所以我们一般都使用PreparedStatement。
常用方法
- addBatch()
把当前 sql 语句加入到一个批处理中。 - execute()
执行当前 SQL,返回个 boolean 值 - executeUpdate()
运行 insert/update/delete 操作,返回更新的行数。 - executeQuery()
执行当前的查询,返回一个结果集对象 - setDate(int parameterIndex, Date x)
向当前SQL语句中的指定位置绑定一个java.sql.Date值 - setDouble(int parameterIndex, double x)
向当前 SQL 语句中的指定位置绑定一个 double值 - setFloat(int parameterIndex, float x)
向当前 SQL 语句中的指定位置绑定一个 float 值 - setInt(int parameterIndex, int x)
向当前 SQL 语句中的指定位置绑定一个 int 值 - setString(int parameterIndex, String x)
向当前 SQL 语句中的指定位置绑定一个 String 值
ResultSet 接口
ResultSet 用来暂时存放数据库查询操作获得结果集。
常用方法
- getString(int index)、getString(String columnName)
获得在数据库里是 varchar、char 等类型的数据对象。 - getFloat(int index)、getFloat(String columnName)
获得在数据库里是 Float 类型的数据对象。 - getDate(int index)、getDate(String columnName)
获得在数据库里是 Date 类型的数据。 - getBoolean(int index)、getBoolean(String columnName)
获得在数据库里是 Boolean 类型的数据。 - getObject(int index)、getObject(String columnName)
获取在数据库里任意类型的数据。
JDBC编写步骤
注:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
获取连接
Properties文件的使用
Properties工具类
Properties工具类,位于java.util包中,该工具类继承Hashtable<Object,Object>。通过Properties工具类可以读取.properties类型的配置文件。
Properties工具类中常用方法
load(InputStream is)
通过给定的输入流对象读取properties文件并解析
getProperty(String key)
根据key获取对应的value
注意:
如果properties文件中含有中文那么需要对idea进行设置。
properties文件内容
#连接Mysql数据库的URL
url=jdbc:mysql://localhost:3306/itbz
#连接数据库的用户名
username=root
#连接数据库的密码
pwd=root
#数据库驱动名称
driver=com.mysql.jdbc.Driver
获取连接
public class JdbcTest {
public static void main(String[] args) {
Connection connection = null;
try {
// 读取jdbc.properties配置文件
InputStream is = JdbcTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
// 获取配置信息
String driver = prop.getProperty("driver"); // 驱动程序
String url = prop.getProperty("url"); // 数据库连接URL
String username = prop.getProperty("username"); // 数据库用户名
String password = prop.getProperty("password"); // 数据库密码
// 加载驱动类
Class.forName(driver);
// 建立数据库连接
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
// 关闭数据库连接
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
}
封装JDBC工具类
public class JDBCUtils {
private static String url;
private static String username;
private static String password;
//加载数据库配置文件信息
static {
try {
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
//加载流文件
prop.load(is);
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
String dirvername = prop.getProperty("driver");
//加载数据库的驱动
Class.forName(dirvername);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取数据库的连接
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
//关闭连接对象
public static void closeConnection(Connection conn){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//提交事务
public static void commit(Connection connection){
try {
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//回滚事务
public static void rollback(Connection connection){
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭PreparedStatament对象
public static void closePreparedStatament(PreparedStatement ps){
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭ResultSet对象
public static void closeResultSet(ResultSet rs){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// DML操作时关闭资源
public static void closeResource(ResultSet rs, PreparedStatement ps,Connection conn){
closeResultSet(rs);
closePreparedStatament(ps);
closeConnection(conn);
}
}
Statement的使用
Statement简介
Statement接口特点
用于执行静态 SQL 语句并返回它所生成结果的对象。 由createStatement 创建,用于发送简单的 SQL 语句(不支持动态绑定)。
注意:
由于Statement对象是一个执行静态SQL语句的对象,所以该对象存在SQL注入风险。
JDBC中三种Statement对象
- Statement:用于执行静态 SQL 语句。
- PreparedStatement:用于执行预编译SQL语句。
- CallableStatement:用于执行数据库存储过程。
PreparedStatement的使用(重点)
PreparedStatement对象简介
继承自 Statement 接口,由 preparedStatement方法创建。
PreparedStatement具有预编译SQL语句能力,所以PreparedStatement 对象比 Statement 对象的效率更高,由于实现了动态的参数绑定,所以可以防止 SQL 注入,所以我们一般都使用 PreparedStatement。
PreparedStatement对象的特点:
- PreparedStatement 接口继承 Statement 接口
- PreparedStatement 效率高于 Statement
- PreparedStatement 支持动态绑定参数
- PreparedStatement 具备 SQL 语句预编译能力
- 使用 PreparedStatement 可防止出现 SQL 注入问题
PreparedStatement 的预编译能力
语句的执行步骤
- 语法和语义解析
- 优化 sql 语句,制定执行计划
- 执行并返回结果
但是很多情况,我们的一条 sql 语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。 如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行 了。所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将 sql 语句模板化或者说参数化预编译语句的优势在于:一次编译、多次运行,省去了解析优化等过程;此外预编译语 句能防止 sql 注入
通过PreparedStatement添加数据
public class PreparedStatementTest {
/**
* 添加用户
*/
public void insertUsers(String username, int userage) {
Connection connection = null;
PreparedStatement ps = null;
try {
//获取数据库连接
connection = JDBCUtils.getConnection();
//定义Sql。?是PreparedStatement对象中的绑定参数的占位符。问号的位置是从1开始计数的
String sql = "insert into users values(default,?,?)";
//创建PreparedStatement对象
ps = connection.prepareStatement(sql);
//完成参数的绑定
ps.setString(1, username);
ps.setInt(2, userage);
int i = ps.executeUpdate();
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps,connection);
}
}
}
ResultSet的使用
ResultSet简介
ResultSet接口的特点
ResultSet用来存放数据库查询操作获得结果集,通过对ResultSet的操作可以获取查询到的结果集数据。
注意:
ResultSet 对象中存放的并不是我们查询到的所有的结果集。它采用分块加载的方式来载入结果集数据。
ResultSet特点
- ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。
- 默认的 ResultSet 对象仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。
- ResultSet 接口提供用于获取当前行检索列值的获取方法(getBoolean、getLong 等)。可以使用列的索引位置或列的名称检索值。
ResultSet使用原理
通过ResultSet获取查询结果
public class ResultSetTest {
/**
* 查询所用用户
*/
public void selectUsersAll(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try{
//获取数据库连接
connection = JDBCUtils.getConnection();
//创建PreparedStatement对象
ps = connection.prepareStatement("select * from users");
//执行查询
resultSet = ps.executeQuery();
//操作ResultSet对象获取查询的结果集
while(resultSet.next()){
//获取列中的数据
int userid = resultSet.getInt("userid");
String username = resultSet.getString("username");
int userage = resultSet.getInt("USERAGE");
System.out.println(userid+" "+username+" "+userage);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.closeResource(resultSet,ps,connection);
}
}
}
ORM编程思想
ORM简介
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/R mapping)是一种为了解决面向对象语言与关系数据库存在的互不匹配的现象。
实体类
实体类就是一个定义了属性,拥有getter、setter、无参构造方法(基本必备)的一个类。实体类可以在数据传输过程中对数据进行封装,相当于一个“工具”、“容器”、“载体”,能存储、传输数据,能管理数据。
实体类特点:
- 实体类名,尽量和数据库中的表名一一对应
- 实体类中的属性对应数据库表中的字段,相关的命名最好也一一对应
- 实体类内方法主要有,getter、setter方法,用于设置、获取数据
- 实体类属性一般为private类型,方法为public类型
- 实体类应该有,无参、有参构造方法
Users实体类
/**
* 实体类,存放Users表中的数据
*/
public class Users {
private int userid;
private String username;
private int userage;
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
}
ORM映射
public List<Users> selectUsersAll(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
List<Users> list = new ArrayList<>();
try{
//获取数据库连接
connection =JdbcUtils.getConnection();
//创建PreparedStatement对象
ps =connection.prepareStatement("select * from users");
//执行查询
resultSet = ps.executeQuery();
//操作ResultSet对象获取查询的结果集
while(resultSet.next()){
//获取列中的数据
int userid =resultSet.getInt("userid");
String username =resultSet.getString("username");
int userage =resultSet.getInt("USERAGE");
//System.out.println(userid+" "+username+""+userage);
//ORM映射处理
Users users = new Users();
users.setUserid(userid);
users.setUsername(username);
users.setUserage(userage);
list.add(users);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeResource(resultSet,ps,connection);
}
return list;
}
SQL注入
什么是SQL注入
所谓 SQL 注入,就是通过把含有 SQL 语句片段的参数插入到需要执行的 SQL 语句中,
最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。
SQL注入案例
/**
* SQL注入测试类
*/
public class SqlInjectTest {
/**
* 体现sql注入
*/
public void sqlInject(String username,int userage){
Connection connection =null;
Statement statement =null;
ResultSet resultSet =null;
try{
//获取连接
connection =JdbcUtils.getConnection();
//创建Statement对象
statement =connection.createStatement();
//定义sql语句
String sql ="select * from users where username ='"+username+"' and userage ="+userage;
System.out.println(sql);
//执行sql语句
resultSet =statement.executeQuery(sql);
//处理结果集
while(resultSet.next()){
int userid =resultSet.getInt("userid");
String name =resultSet.getString("username");
int age =resultSet.getInt("userage");
System.out.println(userid+""+name+" "+age);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeResource(resultSet,statement,connection);
}
}
public static void main(String[] args) {
SqlInjectTest sit = newSqlInjectTest();
sit.sqlInject("oldlu' or 1=1 --",28);
}
}
JDBC批量添加数据
批量添加数据简介
在JDBC中通过PreparedStatement的对象的addBatch()和executeBatch()方法进行数据的批量插入。
- addBatch()把若干SQL语句装载到一起,然后一次性传送到数据库执行,即是批量处理sql数据的。
- executeBatch()会将装载到一起的SQL语句执行。
注意:
MySql默认情况下是不开启批处理的。
数据库驱动从5.1.13开始添加了一个对rewriteBatchStatement
的参数的处理,该参数能够让MySql开启批处理。在url中添加
该参数:rewriteBatchedStatements=true
Mysql的URL参数说明
实现数据的批量添加
在url中开启批量添加
rewriteBatchedStatements=true
实现数据的批量添加方式一
/**
* 批量添加数据方式一
*/
@Test
public void addBatch1() {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement("insert into users values (default,?,?,?)");
for (int i = 0; i < 1000; i++) {
ps.setString(1,"gb"+i);
ps.setString(2,"123456");
ps.setString(3,"@qq.com"+i);
//缓存sql
ps.addBatch();
}
//执行sql
ps.executeBatch();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs,ps,connection);
}
}
实现数据的批量添加方式一
/**
* 批量添加数据方式二
*/
@Test
public void addBatch2(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement("insert into users values (default,?,?,?)");
for (int i = 1; i <= 1000; i++) {
ps.setString(1,"gb"+i);
ps.setString(2,"123456");
ps.setString(3,"@qq.com"+i);
//缓存sql
ps.addBatch();
if (i%500==0){
//执行sql
ps.executeBatch();
//清除缓存
ps.clearBatch();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs,ps,connection);
}
}
JDBC事务处理
事务简介
- 事务:
事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 - 事务操作流程:
- 开启事务
- 提交事务
- 回滚事务
JDBC中事务处理特点
在JDBC中,使用Connection对象来管理事务,默认为自动提交事务。可以通过setAutoCommit(boolean autoCommit)方法设置事务是否自动提交,参数为boolean类型,默认值为true,表示自动提交事务,如果值为false则表示不自动提交事务,需要通过commit方法手动提交事务或者通过rollback方法回滚事务。
JDBC事务处理实现
/**
* 批量添加数据方式二
* 支持事务处理
*/
@Test
public void addBatch2(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = JDBCUtils.getConnection();
//设置事务的提交方式,将自动提交修改为手动提交
connection.setAutoCommit(false);
ps = connection.prepareStatement("insert into users values (default,?,?,?)");
for (int i = 1; i <= 1000; i++) {
ps.setString(1,"gb"+i);
ps.setString(2,"123456");
ps.setString(3,"@qq.com"+i);
//缓存sql
ps.addBatch();
if (i%500==0){
//执行sql
ps.executeBatch();
//清除缓存
ps.clearBatch();
}
if(i==501){
String str = null;
str.length();
}
}
//提交事务
JDBCUtils.commit(connection);
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JDBCUtils.rollback(connection);
} finally {
JDBCUtils.closeResource(rs,ps,connection);
}
}
Blob类型的使用
MySql Blob类型简介
Blob(全称:Binary Large Object 二进制大对象)。在MySql中,Blob是一个二进制的用来存储图片,文件等数据的数据类型。操作Blob类型的数据必须使用PreparedStatement,因为Blob类型的数据无法使用字符串拼接。大多数情况,并不推荐直接把文件存放在MySQL 数据库中,但如果应用场景是文件与数据高度耦合,或者对文件安全性要求较高的,那么将文件与数据存放在一起,即安全,又方便备份和迁移。
Mysql中的Blob类型
MySql中有四种Blob类型,它们除了在存储的最大容量上不同,其他是一致的。
类型 | 大小 |
---|---|
TinyBlob | 最大255字节 |
Blob | 最大65K |
MediumBlob | 最大16M |
LongBlob | 最大4G |
Blob类型使用的注意事项
- 实际使用中根据需要存入的数据大小定义不同的Blob类型。
- 如果存储的文件过大,数据库的性能会下降。
插入Blob类型数据
创建表
CREATE TABLE `movie` (
`movieid` int(11) NOT NULL AUTO_INCREMENT,
`moviename` varchar(30) DEFAULT NULL,
`poster` mediumblob,
PRIMARY KEY (`movieid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
通过PreparedStatement存储Blob类型数据
public class BlobTest {
public void insertMovie(String movieName, InputStream is) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn= JDBCUtils.getConnection();
ps = conn.prepareStatement("insert into movie values (default,?,?)");
//绑定参数
ps.setString(1,movieName);
ps.setBlob(2,is);
ps.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(null,ps,conn);
}
}
public static void main(String[] args) throws FileNotFoundException {
BlobTest bt = new BlobTest();
//创建读取文件的IO流
InputStream is = new FileInputStream(new File("D:\\ASUS\\Pictures\\DSC_0664.JPG"));
bt.insertMovie("战狼",is);
}
}
解除文件大小限制
虽然MediumBlob允许保存最大值为16M,但MySql中默认支持的容量为4194304即4M。我们可以通过修改Mysql的my.ini文件中max_allowed_packet属性扩大支持的容量,修改完毕后需要重启MySql服务。
文件位置
修改属性
读取Blob类型数据
/**
* 根据影片ID查询影片信息
*
* @param movieid
*/
public void selectMovieById(int movieid) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement("select * from movie where movieid=?");
ps.setInt(1,movieid);
rs = ps.executeQuery();
while (rs.next()){
int id = rs.getInt("movieid");
String name = rs.getString("moviename");
System.out.println(id+" "+name);
//获取blob类型的数据
Blob blob = rs.getBlob("poster");
InputStream is = blob.getBinaryStream();
//创建文件输出字节流对象
OutputStream os = new FileOutputStream(id+"_"+name+".jpg");
//操作流完成文件的输出处理
byte[] bytes = new byte[1024];
int len=0;
while ((len=is.read(bytes))!=-1){
os.write(bytes,0,len);
}
os.flush();
is.close();
os.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, ps, connection);
}
}
其他查询方式
模糊查询
/**
* 模糊查询测试类
*/
public class FuzzyQueryTest {
/**
* 根据用户名称模糊查找用户信息
*/
public List<Users> fuzzyQuery(String username){
Connection connection=null;
PreparedStatement ps = null;
ResultSet rs =null;
List<Users> list = new ArrayList<>();
try {
connection= JDBCUtils.getConnection();
ps = connection.prepareStatement("select * from users where username Like ?");
ps.setString(1,username);
rs = ps.executeQuery();
while (rs.next()){
Users user = new Users();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
list.add(user);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(rs, ps, connection);
}
return list;
}
public static void main(String[] args) {
FuzzyQueryTest ft = new FuzzyQueryTest();
List<Users> users = ft.fuzzyQuery("%g%");
users.forEach(System.out::println);
}
}
结果:
动态条件查询
动态条件查询实现
/**
* 动态条件查询测试类
*/
public class DynamicConditionQueryTest {
/**
* 动态条件查询Users
*
* @param users
* @return
*/
public List<Users> queryUsers(Users users) {
List<Users> list = new ArrayList<>();
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = JDBCUtils.getConnection();
String sql = this.generateSql(users);
System.out.println(sql);
ps = connection.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()){
Users user = new Users();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
list.add(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, ps, connection);
}
return list;
}
/**
* 动态条件查询Users
*/
private String generateSql(Users users) {
StringBuilder sb = new StringBuilder("select * from users where 1=1");
if (users.getId()>0){
sb.append(" and id=").append(users.getId());
}
if (users.getUsername()!=null && users.getUsername().length()>0){
sb.append(" and username = '").append(users.getUsername()).append("' ");
}
if (users.getPassword()!=null && users.getPassword().length()>0){
sb.append(" and password= '").append(users.getPassword()).append("' ");
}
return sb.toString();
}
public static void main(String[] args) {
DynamicConditionQueryTest dt = new DynamicConditionQueryTest();
Users users = new Users();
users.setId(1);
users.setUsername("gb");
users.setPassword("123");
// users.setEmail("123");
List<Users> list = dt.queryUsers(users);
list.forEach(System.out::println);
}
}
分页查询
分页查询简介
当一个操作数据库进行查询的语句返回的结果集内容如果过多,那么内存极有可能溢出,所以在查询中含有大数据的情况下分页是必须的。
分页查询分类:
- 物理分页:
- 在数据库执行查询时(实现分页查询),查询需要的数据—依赖数据库的SQL语句
- 在SQL查询时,从数据库只检索分页需要的数据
- 通常不同的数据库有着不同的物理分页语句
- MySql物理分页采用limit关键字
- 逻辑分页:
- 在sql查询时,先从数据库检索出所有数据的结果集,在程序内,通过逻辑语句获得分页需要的数据
如何在MySql中实现物理分页查询
使用limit方式。
使用limit方式。
select * from tableName limit m,n
其中m与n为数字。n代表需要获取多少行的数据项,而m代表从哪开始(以0为起始)。
分页公式:(当前页-1)*每页大小
创建Page模型
/**
* 分页查询实体类
*/
public class Page {
//当前页
private int currentPage;
//每页显示的条数
private int pageSize;
//总条数
private int totalCount;
//总页数
private int totalPage;
//结果集
private List<T> result;
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public List<T> getResult() {
return result;
}
public void setResult(List<T> result) {
this.result = result;
}
}
实现分页查询
分页实现
/**
* 分页查询测试类
*/
public class PageTest {
/**
* 分也查询Users
*/
public Page<Users> selectPage(Page page){
Connection connection =null;
PreparedStatement ps = null;
ResultSet rs= null;
List<Users> list = new ArrayList<>();
try {
connection=JDBCUtils.getConnection();
ps=connection.prepareStatement("select * from users limit ?,?");
//绑定m参数 m的值 = (当前页-1)*每页=显示的条数
ps.setInt(1,(page.getCurrentPage()-1)*page.getPageSize());
//绑定n参数 n的值为每页显示的条数
ps.setInt(2,page.getPageSize());
rs = ps.executeQuery();
while (rs.next()){
Users user = new Users();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
list.add(user);
}
//讲结果集存放到Page对象中。
page.setResult(list);
//查询总条数
ps =connection.prepareStatement("select count(*) from users");
//执行查询
rs = ps.executeQuery();
while (rs.next()){
//总条数
int count = rs.getInt(1);
//保存总条数
page.setTotalCount(count);
//换算总页数=总条数/每页显示的条数 向上取整
int totalPage = (int)Math.ceil(1.0*count/page.getPageSize());
page.setTotalPage(totalPage);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(rs,ps,connection);
}
return page;
}
public static void main(String[] args) {
PageTest pt = new PageTest();
Page page = new Page();
page.setCurrentPage(1);
page.setPageSize(2);
Page page1 = pt.selectPage(page);
System.out.println("总条数:"+page1.getTotalCount());
System.out.println("总页数:"+page1.getTotalPage());
System.out.println("当前页:"+page1.getCurrentPage());
System.out.println("每页显示的条数:"+page1.getPageSize());
List<Users> list = page1.getResult();
list.forEach(System.out::println);
}
}
数据库连接池
数据库连接池简介
什么是数据库连接池
数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
不使用数据库连接池存在的问题
-
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码,所以整个过程比较耗时。
-
需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
-
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
JDBC数据库连接池的必要性
-
**数据库连接池的基本思想:**为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
-
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连
接,而不是重新建立一个。 -
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的优点
-
资源重用: 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
-
更快的系统反应速度: 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。
-
新的资源分配手段: 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置实现某一应用最大可用数据库连接数的限制避免某一应用独占所有的数据库资源.
-
统一的连接管理: 避免数据库连接泄露在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
常用的数据库连接池
- c3p0:是一个开源组织提供的数据库连接池,速度相对较慢,稳定性还可以。
- DBCP:是Apache提供的数据库连接池。速度相对c3p0较快,但自身存在bug。
- Druid:是阿里提供的数据库连接池,据说是集DBCP、c3p0优点于一身的数据库连接池,目前经常使用。
Druid连接池
Druid简介
Druid是阿里提供的数据库连接池,它结合了C3P0、DBCP等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。
Druid使用
创建一个druid.properties类型的文件,并写入
url=jdbc:mysql://localhost:3306/test01_bookstore
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=521314
initialSize=10
maxActive=20
druid配置信息:
通过数据库连接池获取连接
public class DruidTest {
public static void main(String[] args) throws Exception {
InputStream is = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
Connection connection = dataSource.getConnection();
}
}
通过druid重构JDBCUtils
/**
* 基于数据库连接池获取数据库连接工具
*/
public class JdbcDruidUtils {
//数据库连接对象
private static DataSource dataSource;
static {
try {
InputStream is = JdbcDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
//获取连接池对象
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取数据库连接
public static Connection getConnection() {
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
//关闭数据库连接对象
public static void closeConnection(Connection connection) {
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
//提交事务
public static void commit(Connection connection) {
try {
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//回滚事务
public static void rollback(Connection connection) {
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭PreparedStatament对象
public static void closePreparedStatament(PreparedStatement ps) {
if (ps!=null){
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
//关闭ResultSet对象
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
// DML操作时关闭资源
public static void closeResource(ResultSet rs, PreparedStatement ps, Connection conn) {
closeResultSet(rs);
closePreparedStatament(ps);
closeConnection(conn);
}
}
应用程序分层
应用程序分层简介
应用程序分层是指通过创建不同的包来实现项目的分层,将项目中的代码根据功能做具体划分,并存放在不同的包下。
三层结构
三层结构就是将整个业务应用划分为:表述层、业务逻辑层 、数据访问层。区分层次的目的即为了“高内 聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。
分层优点
-
分层结构将应用系统划分为若干层,每一层只解决问题的一部分,通过各层的协作提供整体解决方案。大的问题被分解为一系列相对独立的子问题,局部化在每一层中,这样就有效的降低了单个问题的规模和复杂度,实现了复杂系统的第一步也是最为关键的一步分解。
-
分层结构具有良好的可扩展性,为应用系统的演化增长提供了一个灵活的支持,具有良好的可扩展性。增加新的功能时,无须对现有的代码做修改,业务逻辑可以得到最大限度的重用。
-
分层结构易于维护。在对系统进行分解后,不同的功能被封装在不同的层中,层与层之间的耦合显著降低。因此在修改某个层的代码时,只要不涉及层与层之间的接口,就不会对其他层造成严重影响。
分层命名
- 表述层:web或controller
- 业务层:service
- 数据访问层:dao (Data Access Object)
应用程序分层实现
在分层项目中实现查询业务
UserDao接口
封装通用的BaseDao
BaseDao接口
public interface BaseDao{
/**
* 通用的DML操作方法
*/
int executeUpdate(String sql,Object...args);
/**
* 通用的查询方法(一)
* 要求:实体类的属性名必须要与表的列名相同。
* 通过BeanUtil工具类讲值映射到对象中
*/
<T> List<T> select1(String sql, Class<T> clazz, Object...args);
/**
* 通用的查询方法(二)
*/
<T> List<T> select2(String sql, Class<T> clazz, Object...args);
/**
* 通用的查询方法
* 返回一个对象
*/
<T> T select(String sql,Class<T> clazz,Object...args);
}
BaseDaoImpl接口实现类
public class BaseDaoImpl implements BaseDao {
/**
* 通用的DML操作方法
*
* @param sql
* @param args
* @return
*/
@Override
public int executeUpdate(String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
int row = 0;
try {
conn = JdbcDruidUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
row = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
//通过自定义异常解决异常耦合问题
throw new ApplicationException(e.getMessage());
} finally {
JdbcDruidUtils.closeResource(rs, ps, conn);
}
return row;
}
/**
* 通用的查询方法(一)
* 要求:实体类的属性名必须要与表的列名相同。
* 通过BeanUtil工具类讲值映射到对象中
*/
@Override
public <T> List<T> select1(String sql, Class<T> clazz, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = JdbcDruidUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集信息
ResultSetMetaData rm = rs.getMetaData();
while (rs.next()){
//通过ORM映射
//通过反射实例化实体类对象
T instance = clazz.newInstance();
for (int i = 0; i <rm.getColumnCount(); i++) {
//得到列名
String columnName = rm.getColumnName(i + 1);
//获取列的值
Object value = rs.getObject(columnName);
//通过BeanUtils 工具类将值映射到对象中
BeanUtils.setProperty(instance,columnName,value);
}
list.add(instance);
}
} catch (Exception e) {
e.printStackTrace();
throw new ApplicationException(e.getMessage());
} finally {
JdbcDruidUtils.closeResource(rs,ps,conn);
}
return list;
}
@Override
public <T> List<T> select2(String sql, Class<T> clazz, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = JdbcDruidUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集信息
ResultSetMetaData rm = rs.getMetaData();
while (rs.next()){
//通过ORM映射
//通过反射实例化实体类对象
T instance = clazz.newInstance();
for (int i = 0; i < rm.getColumnCount(); i++) {
String columnName = rm.getColumnLabel(i + 1);
Object value = rs.getObject(columnName);
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
if (field.getType() == java.util.Date.class && value instanceof LocalDateTime) {
// 将LocalDateTime转换为Timestamp,再转换为java.util.Date类型
LocalDateTime localDateTime = (LocalDateTime) value;
Timestamp timestamp = Timestamp.valueOf(localDateTime);
value = new java.util.Date(timestamp.getTime());
}
field.set(instance, value);
}
list.add(instance);
}
} catch (Exception e) {
e.printStackTrace();
throw new ApplicationException(e.getMessage());
} finally {
JdbcDruidUtils.closeResource(rs,ps,conn);
}
return list;
}
@Override
public <T> T select(String sql, Class<T> clazz, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
T instance=null;
try {
conn = JdbcDruidUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集信息
ResultSetMetaData rm = rs.getMetaData();
while (rs.next()){
//通过ORM映射
//通过反射实例化实体类对象
instance = clazz.newInstance();
for (int i = 0; i <rm.getColumnCount(); i++) {
//得到列名
String columnName = rm.getColumnLabel(i + 1);
//获取列的值
Object value = rs.getObject(columnName);
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(instance,value);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new ApplicationException(e.getMessage());
} finally {
JdbcDruidUtils.closeResource(rs,ps,conn);
}
return instance;
}
}
对象的关联关系
关联关系简介
关联关系(Association),是一种拥有的关系,它使一个对象知道另一个对象的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。
对象的关联关系解决了什么问题
在多表查询时,使用对象关联关系能够更合理的存放查询到的结果集数据。
关联关系的方向性
- 单向
只在一侧关联了对方。 - 双向
两侧相互关联了对方。
创建对象的关联关系
创建表
orders表
CREATE TABLE `orders` (
`orderid` int(11) NOT NULL AUTO_INCREMENT,
`orderprice` float(11,2) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`orderid`),
KEY `orders_fk` (`user_id`),
CONSTRAINT `orders_fk` FOREIGN KEY
(`user_id`) REFERENCES `users` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
items表
CREATE TABLE `items` (
`itemid` int(11) NOT NULL,
`itemname` varchar(30) DEFAULT NULL,
`itemprice` float(11,2) DEFAULT NULL,
`itemnum` int(11) DEFAULT NULL,
PRIMARY KEY (`itemid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
orders_itmes表
CREATE TABLE `orders_items` (
`order_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
PRIMARY KEY (`order_id`,`item_id`),
KEY `orders_items_fk2` (`item_id`),
CONSTRAINT `orders_items_fk` FOREIGN KEY
(`order_id`) REFERENCES `orders` (`orderid`),
CONSTRAINT `orders_items_fk2` FOREIGN KEY
(`item_id`) REFERENCES `items` (`itemid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
对象的关联关系
Users对象
public class Users {
private int userid;
private String username;
private int userage;
//创建与Orders的关联关系
private List<Orders> orders = newArrayList<>();
public List<Orders> getOrders() {
return orders;
}
public void setOrders(List<Orders> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Users{" +
"userid=" + userid +
", username='" + username +'\'' +
", userage=" + userage +
'}';
}
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username){
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
}
Orders对象
public class Orders {
private int orderid;
private double orderprice;
//关联Users
private Users users;
//关联Items
private List<Items> items =new ArrayList<>();
public int getOrderid() {
return orderid;
}
public void setOrderid(int orderid) {
this.orderid = orderid;
}
public double getOrderprice() {
return orderprice;
}
public void setOrderprice(double orderprice) {
this.orderprice = orderprice;
}
public Users getUsers() {
return users;
}
public void setUsers(Users users) {
this.users = users;
}
public List<Items> getItems() {
return items;
}
public void setItems(List<Items> items){
this.items = items;
}
}
Items对象
public class Items {
private int itemid;
private String itemname;
private double itemprice;
private int itemnum;
//关联Orders
private List<Orders> orders = new ArrayList<>();
public int getItemid() {
return itemid;
}
public void setItemid(int itemid) {
this.itemid = itemid;
}
public String getItemname() {
return itemname;
}
public void setItemname(String itemname){
this.itemname = itemname;
}
public double getItemprice() {
return itemprice;
}
public void setItemprice(doubleitemprice) {
this.itemprice = itemprice;
}
public int getItemnum() {
return itemnum;
}
public void setItemnum(int itemnum) {
this.itemnum = itemnum;
}
public List<Orders> getOrders() {
return orders;
}
public void setOrders(List<Orders> orders) {
this.orders = orders;
}
}
使用对象关联关系存放查询数据
需求:查询用户ID为1的用户信息他的订单信息,以及订单中所包含的商品信息。
SQL语句
select * from users u,orders o, orders_items oi, items i
WHERE u.userid = o.user_id
and o.orderid =oi.order_id
and oi.item_id = i.itemid
and u.userid =1
UserDao接口
/**
* 查询用户ID为1的用户信息他的订单信息,
* 以及订单中所包含的商品信息。
*/
Users selectUsers(int userid);
UsersDaoImpl接口实现类
/**
* 查询用户ID为1的用户信息他的订单信息,
* 以及订单中所包含的商品信息。
*/
@Override
public Users selectUsers(int userid) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Users users =new Users();
try{
conn =JdbcDruidUtil.getConnection();
ps =conn.prepareStatement("select * from users u,orders o, orders_items oi, items i WHERE\n" +"u.userid = o.user_id and o.orderid = oi.order_id and oi.item_id = i.itemid \n" +"and u.userid =?");
ps.setInt(1,userid);
rs = ps.executeQuery();
while(rs.next()){
//Users对象的ORM映射
users.setUserid(rs.getInt("userid"));
users.setUsername(rs.getString("username"));
users.setUserage(rs.getInt("userage"));
//Orders对象的ORM映射
Orders orders = new Orders();
orders.setOrderid(rs.getInt("orderid"));
orders.setOrderprice(rs.getDouble("orderprice"));
users.getOrders().add(orders);
//Items对象的ORM映射
Items items = new Items();
items.setItemid(rs.getInt("itemid"));
items.setItemname(rs.getString("itemname"));
items.setItemprice(rs.getDouble("itemprice"));
items.setItemnum(rs.getInt("itemnum"));
orders.getItems().add(items);
}
}catch (Exception e){
e.printStackTrace();
//通过自定义异常解决异常耦合问题
throw new ApplicationException(e.getMessage());
}finally{
JdbcDruidUtil.closeResource(rs,ps,conn);
}
return users;
}