JDBC概述
数据的持久化
- 持久化(persistence):将内存中的数据保存到可永久保存的存储
设备中(如磁盘)。 - 持久化的主要应用是将内存中的数据存储在关系型数据库中,当
然也可以存储在磁盘文件、XML数据文件中。
什么是 JDBC
- JDBC(Java DataBase Connectivity)java 数据库连接
- 是 JavaEE 平台下的技术规范
- 定义了在 Java 语言中连接数据库,执行 SQL 语句的标准 API
- 可以为多种关系数据库提供统一访问
什么是数据库驱动程序
- 数据库驱动就是直接操作数据库的一个程序
- 不同数据产品的数据库驱动名字有差异
- 在程序中需要依赖数据库驱动来完成对数据库的操作
Java中访问数据库技术
- 基于JDBC标准访问数据库
- 使用第三方ORM 框架,如Hibernate, Mybatis 等访问数据库
程序操作数据库流程
如果没有JDBC,那么Java程序访问数据库时是这样的:
有了JDBC,Java程序访问数据库时是这样的:
JBDC中常用的类与接口
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”);
3.Connection接口
Connection 是数据库的连接(会话)对象。对数据库的一切操
作都是在这个连接基础之上进行的,我们可以通过该对象执行
sql 语句并返回结果。
常用方法
- createStatement()
创建向数据库发送 sql 的 Statement 接口类型的对象。 - preparedStatement(sql)
创建向数据库发送预编译 sql 的 PrepareSatement 接口类型
的对象。 - setAutoCommit(boolean autoCommit)
设置事务是否自动提交。 - commit()
在链接上提交事务。 - rollback()
在此链接上回滚事务。
4.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 语句执行。
5.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 值
6.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 驱动程序将调用转换成为对特定的数据库的
调用请求。
获取连接
环境:
数据库Mysql5.7
数据库驱动版本5.1.48
数据库名xcm
准备工作:
新建JavaProject工程
添加数据库驱动jar包
获取数据库连接对象
获取数据库连接
package com.xcm;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 获取数据库连接测试类
*/
public class JdbcTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//连接Mysql数据库的URL
String url="jdbc:mysql://localhost:3306/db";
//连接数据库的用户名
String name="root";
//连接数据库的密码
String pwd="1234";
//通过反射实现数据库驱动的加载与注册
Class.forName("com.mysql.jdbc.Driver");
//通过DriverManager对象获取数据库连接对象
Connection connection = DriverManager.getConnection(url, name, pwd);
System.out.println(connection);
}
}
在加载com.mysql.jdbc.Driver类信息时,会执行静态块中的代码。在静态块中 ,数据库驱动会实例化自己并通过DriverManager的registerDriver方法,将自己注册DriverManager驱动管理器中。
Properties文件的使用
properties文件介绍
后缀properties的文件是一种属性文件。这种文件以key=value格式存储内容。Java中可以使用Properties工具类来读取这个文件。项目中会将一些配置信息放到properties文件中,所以properties文
件经常作为配置文件来使用。
Properties工具类
Properties工具类,位于java.util包中,该工具类继承自Hashtable<Object,Object>。通过Properties工具类可以读取.properties类型的配置文件。
Properties工具类中常用方法
load(InputStream is)
通过给定的输入流对象读取properties文件并解析
getProperty(String key)
根据key获取对应的value
如果properties文件中含有中文那么需要对idea进行设置。
右击src,新建File,命名必须以.properties结尾
properties文件
# 我爱看熊出没
key1=XCM
key2=XD
key3=XE
操作properties文件
package com.xcm;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 读取properties配置文件的测试类
*/
public class PropertiesTest {
public static void main(String[] args) throws IOException {
//实例化Properties对象
Properties prop=new Properties();
//获取读取properties文件的输入流对象
InputStream is=PropertiesTest.class.getClassLoader().getResourceAsStream("test.properties");
//通过给定的输入流对象读取properties文件并解析
prop.load(is);
//获取properties文件中的内容
String value1=prop.getProperty("key1");
String value2=prop.getProperty("key2");
String value3=prop.getProperty("key3");
System.out.println(value1+" "+value2+" "+value3);
}
}
优化获取数据库连接
将连接数据库时所需要的信息存放到properties文件中,可以解决硬编码的问题。
properties文件内容
#连接Mysql数据库的URL
url=jdbc:mysql://localhost:3306/db
#连接数据库的用户名
username=root
#连接数据库的密码
pwd=1234
#数据库驱动名称
driver=com.mysql.jdbc.Driver
获取连接
package com.xcm;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* 优化获取数据库连接
*/
public class JdbcTest2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//实例化Properties对象
Properties prop=new Properties();
//获取读取properties文件的字节输入流对象
InputStream is=JdbcTest2.class.getClassLoader().getResourceAsStream("jdbc.properties");
//读取properties文件
prop.load(is);
//获取连接数据库的url
String url=prop.getProperty("url");
//获取连接数据库的用户名
String name=prop.getProperty("username");
//获取连接数据库的密码
String pwd=prop.getProperty("pwd");
//获取数据库驱动全名
String drivername=prop.getProperty("driver");
//加载并注册驱动
Class.forName(drivername);
//通过驱动管理器对象获取连接对象
Connection connection=DriverManager.getConnection(url,name,pwd);
System.out.println(connection);
}
}
封装JDBC工具类
将获取数据库连接的代码封装成工具类
properties文件内容
#连接Mysql数据库的URL
url=jdbc:mysql://localhost:3306/db
#连接数据库的用户名
username=root
#连接数据库的密码
pwd=1234
#数据库驱动名称
driver=com.mysql.jdbc.Driver
JdbcUtil工具类
package com.xcm;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static String url;
private static String name;
private static String pwd;
static {
try {
//实例化Properties对象
Properties prop = new Properties();
//获取读取properties文件的字节输入流对象
InputStream is = JdbcTest2.class.getClassLoader().getResourceAsStream("jdbc.properties");
//读取properties文件并解析
prop.load(is);
//获取连接数据库的url
url = prop.getProperty("url");
//获取连接数据库的用户名
name=prop.getProperty("username");
//获取连接数据库的密码
pwd=prop.getProperty("pwd");
//获取数据库驱动全名
String drivername=prop.getProperty("driver");
//加载并注册驱动
Class.forName(drivername);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection() {
Connection connection=null;
try {
connection= DriverManager.getConnection(url, name, pwd);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭连接对象
public static void closeConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事物
public static void commit(Connection connection){
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//事物回滚
public static void rollback(Connection connection){
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭Statement对象
public static void closeStatement(Statement statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭ResultSet
public static void closeResultSet(ResultSet resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//DML操作时关闭资源
public static void closeResource(ResultSet resultSet,Statement statement,Connection connection){
//先关闭ResultSet
closeResultSet(resultSet);
//在关闭Statement对象
closeStatement(statement);
//最后关闭Connection对象
closeConnection(connection);
}
public static void closeResource(Statement statement,Connection connection){
//在关闭Statement对象
closeStatement(statement);
//最后关闭Connection对象
closeConnection(connection);
}
}
statement的使用
用于执行静态SQL语句
Statement简介
Statement接口特点
用于执行静态 SQL 语句并返回它所生成结果的对象。 由 createStatement 创建,用于发送简单的 SQL 语句(不支持动态绑定)。
由于Statement对象是一个执行静态SQL语句的对象,所以该对象存在SQL注入风险。
JDBC中三种Statement对象
- Statement:用于执行静态 SQL 语句。
- PreparedStatement:用于执行预编译SQL语句。
- CallableStatement:用于执行数据库存储过程。
通过Statement添加数据
创建表
CREATE TABLE `users`(
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(30) DEFAULT NULL,
`userage` int(11) DEFAULT NULL,
PRIMARY KEY(`userid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
添加数据
package com.xcm;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Statement对象的使用
*/
public class StatementTest {
/**
* 添加用户
*/
public void insertUsers(String username, int userage) {
Connection connection = null;
Statement statement = null;
try {
//获取Connection对象
connection = JdbcUtils.getConnection();
//获取Statement对象
statement = connection.createStatement();
//定义需要执行的SQL语句
String sql = "insert into users values(default,'" + username + "'," + userage + ")";
//执行SQL,返回boolean值,如果sql有结果集返回,返回值为true,若没有结果集返回,则返回false
boolean execute = statement.execute(sql);
System.out.println(execute);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeResource(statement, connection);
}
}
/**
* 修改用户信息
*
* @param userid
* @param username
* @param userage
*/
public void updateUsers(int userid, String username, int userage) {
Connection connection = null;
Statement statement = null;
try {
//获取连接对象
connection = JdbcUtils.getConnection();
//获取Statement对象
statement = connection.createStatement();
//定义sql语句
String sql = "update users set username='" + username + "',userage=" + userage
+ "where userid=" + userid;
//执行sql语句
int i = statement.executeUpdate(sql);
System.out.println(i);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeResource(statement, connection);
}
}
/**
* 根据用户ID删除用户
*/
public void deleteUserById(int userid) {
Connection connection = null;
Statement statement = null;
try {
//获取数据库连接
connection = JdbcUtils.getConnection();
//获取Statement对象
statement = connection.createStatement();
//定义执行删除语句
String sql = "delete from users where userid=" + userid;
//执行sql
int i = statement.executeUpdate(sql);
System.out.println(i);
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.closeResource(statement,connection);
}
}
}
PreparedStatement的使用
具有预编译SQL语句的能力
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添加数据
/**
* 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){
}finally {
JdbcUtils.closeResource(ps,connection);
}
}
}
通过PreparedStatement修改数据
public void updateUserById(int userid,String username,int userage){
Connection connection=null;
PreparedStatement ps=null;
try{
//获取数据库连接对象
connection=JdbcUtils.getConnection();
//创建PreparedStatement对象
ps=connection.prepareStatement("update users set username=?,userage=?,where userid=?");
//参数绑定
ps.setString(1,username);
ps.setInt(2,userage);
ps.setInt(3,userid);
int i=ps.executeUpdate();
System.out.println(i);
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtils.closeResource(ps,connection);
}
}
通过PreparedStatement删除数据
//根据用户ID删除指定用户
public void deleteUserById(int userid) {
Connection conn = null;
PreparedStatement ps = null;
try {
//获取数据库连接对象
conn = JdbcUtils.getConnection();
//创建PreparedStatement对象
ps = conn.prepareStatement("delete from userid where userid=?");
//绑定参数
ps.setInt(1, userid);
int i = ps.executeUpdate();
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeResource(ps, conn);
}
}
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类型
- 实体类应该有,无参、有参构造方法
ORM使用
Users类
package com.xcm;
public class Users {
private int userid;
private String username;
private int userage;
public Users() {
}
public Users(int userid, String username, int userage) {
this.userid = userid;
this.username = username;
this.userage = userage;
}
/**
* 获取
* @return userid
*/
public int getUserid() {
return userid;
}
/**
* 设置
* @param userid
*/
public void setUserid(int userid) {
this.userid = userid;
}
/**
* 获取
* @return username
*/
public String getUsername() {
return username;
}
/**
* 设置
* @param username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* 获取
* @return userage
*/
public int getUserage() {
return userage;
}
/**
* 设置
* @param userage
*/
public void setUserage(int userage) {
this.userage = userage;
}
public String toString() {
return "Users{userid = " + userid + ", username = " + username + ", userage = " + userage + "}";
}
}
ORM映射
//查询所有用户
public List<Users> selectAllUsers() {
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");
//ORM映射处理
Users users=new Users(userid,username,userage);
list.add(users);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.closeResource(resultSet,ps,connection);
}
return list;
}
}
SQL 注入(SQL Injection)
什么是SQL注入
通过把含有 SQL 语句片段的参数插入到需要执行的 SQL 语句中, 最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。
SQL注入案例
package com.xcm;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
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 sqlInjectTest=new SqlInjectTest();
sqlInjectTest.sqlInject("tutu' or 1=1 -- ",12);
}
}
解决SQL注入
public void noSqlInject(String username,int userage){
Connection connection=null;
PreparedStatement ps=null;
ResultSet resultSet=null;
try {
//获取连接
connection=JdbcUtils.getConnection();
//创建PreparedStatement对象
ps=connection.prepareStatement("select * from users where username=? and userage=?");
//绑定参数
ps.setString(1,username);
ps.setInt(2,userage);
//执行sql
resultSet=ps.executeQuery();
//处理结果集
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,ps,connection);
}
}
JDBC批量添加数据
批量添加数据简介
在JDBC中通过PreparedStatement的对象的addBatch()和executeBatch()方法进行数据的批量插入。
- addBatch()把若干SQL语句装载到一起,然后一次性传送到数据库执行,即是批量处理sql数据的。
- executeBatch()会将装载到一起的SQL语句执行。
MySql默认情况下是不开启批处理的。数据库驱动从5.1.13开始添加了一个对rewriteBatchStatement的参数的处理,该参数能够让MySql开启批处理。在url中添加该参数:rewriteBatchedStatements=true
Mysql的URL参数说明
方法 | 参数 | 说明 |
---|---|---|
useUnicode | [true false] | 是否使用编码集,需配合 characterEncoding 参数使用。 |
characterEncoding | [utf-8 gbk …] | 编码类型。 |
useSSL | [true false] | 是否使用SSL协议。 |
rewriteBatchedStatements | [true false] | 可以重写向数据库提交的SQL语句。 |
实现数据的批量添加
在url中开启批量添加
rewriteBatchedStatements=true
实现数据的批量添加方式一
/**
* 批量添加数据方式一
*
*/
public void addBatch1(){
Connection conn=null;
PreparedStatement ps=null;
try{
//创建连接
conn=JdbcUtils.getConnection();
//创建PreparedStatement
ps=conn.prepareStatement("insert into users values(default,?,?)");
//参数绑定
for (int i = 0; i < 10; i++) {
//绑定username
ps.setString(1,"xcm"+i);
//绑定userage
ps.setInt(2,10);
//缓存sql
ps.addBatch();
}
//执行sql
ps.executeBatch();
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtils.closeResource(ps,conn);
}
}
实现数据的批量添加方式二
public void addBatch2(){
Connection conn=null;
PreparedStatement ps=null;
try {
//创建连接
conn=JdbcUtils.getConnection();
//创建PreparedStatement
ps=conn.prepareStatement("insert into users values (default ,?,?)");
//参数绑定
for (int i = 0; i < 100; i++) {
//绑定username
ps.setString(1,"熊出没"+i);
//绑定userage
ps.setInt(2,20);
//缓存sql
ps.addBatch();
if(i%5==0){
//执行sql
ps.executeBatch();
//清除缓存
ps.clearBatch();
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtils.closeResource(ps,conn);
}
}
JDBC事务处理
事务简介
- 事务:作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
- 事务操作流程:开启事物 --> 提交事物 -->回滚事物
JDBC中事务处理特点
在JDBC中,使用Connection对象来管理事务,默认为自动提交事务。可以通过setAutoCommit(boolean autoCommit)方法设置事务是否自动提交,参数为boolean类型,默认值为true,表示自动提交事务,如果值为false则表示不自动提交事务,需要通过commit方法手动提交事务或者通过rollback方法回滚事务。
/**
* 批量添加数据方式二
* 支持事务处理
*/
public void addBatch2(){
Connection conn=null;
PreparedStatement ps=null;
try {
//创建连接
conn=JdbcUtils.getConnection();
//创建PreparedStatement
ps=conn.prepareStatement("insert into users values (default ,?,?)");
//参数绑定
for (int i = 0; i < 100; i++) {
//绑定username
ps.setString(1,"熊出没"+i);
//绑定userage
ps.setInt(2,20);
//缓存sql
ps.addBatch();
if(i%5==0){
//执行sql
ps.executeBatch();
//清除缓存
ps.clearBatch();
}
if(i==6){
String str=null;
str.length();
}
}
//提交事务
JdbcUtils.commit(conn);
}catch (Exception e){
e.printStackTrace();
JdbcUtils.rollback(conn);
}finally {
JdbcUtils.closeResource(ps,conn);
}
}
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) PRIMARY KEY NOT NULL AUTO_INCREMENT,
moviename VARCHAR(30) DEFAULT NULL,
poster MEDIUMBLOB
)
通过PreparedStatement存储Blob类型数据
/**
* Blob类型操作测试类
*/
public class BlobTest {
/**
* 向Movie表中插入数据
*/
public void insertMovie(String moviename, InputStream is){
Connection conn=null;
PreparedStatement ps=null;
try{
//获取连接
conn=JdbcUtils.getConnection();
//创建PreparedStatement
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(ps,conn);
}
}
public static void main(String[] args) throws FileNotFoundException {
BlobTest bt=new BlobTest();
//创建读取文件的IO流
InputStream is=new FileInputStream(new File("d:/2.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 conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
//获取连接
conn=JdbcUtils.getConnection();
//创建PreparedStatement对象
ps=conn.prepareStatement("select * from movie where movieid=?");
//绑定参数
ps.setInt(1,movieid);
//执行sql
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");
//获取能够从Blob类型的列中读取数据的IO流
InputStream is=blob.getBinaryStream();
//创建文件输出字节流对象
OutputStream os=new FileOutputStream(id+"_"+name+".jpg");
//操作流完成文件的输出处理
byte[] buff=new byte[1024];
int len;
while ((len =is.read(buff))!=-1){
os.write(buff,0,len);
}
os.flush();
is.close();
os.close();
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtils.closeResource(rs,ps,conn);
}
}
其他查询:模糊查询
like:模糊查询关键字
%:模糊查询通配符
_:模糊查询占位符
public class FuzzyQueryTest {
/**
* 根据用户名称模糊查找用户信息
*/
public List<Users> fuzzyQuery(String username){
List<Users> list=new ArrayList<>();
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//获取数据库连接
conn = JdbcUtils.getConnection();
//创建PreparedStatement对象
ps = conn.prepareStatement("select * from users where username like ? ");
//参数绑定
ps.setString(1,username);
//执行sql语句
rs=ps.executeQuery();
while (rs.next()){
Users users=new Users();
users.setUserid(rs.getInt("userid"));
users.setUsername(rs.getString("username"));
users.setUserage(rs.getInt("userage"));
list.add(users);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtils.closeResource(rs,ps,conn);
}
return list;
}
public static void main(String[] args) {
FuzzyQueryTest ft=new FuzzyQueryTest();
List<Users> users=ft.fuzzyQuery("%熊%");
for (Users user:users){
System.out.println(user.getUserid()+" "+user.getUsername()+" "+user.getUserage());
}
}
}
动态条件查询
动态条件查询实现
public class DynamicConditionQueryTest {
//动态条件查询Users
public List<Users> queryUsers(Users users) {
List<Users> list = new ArrayList<>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//获取数据库连接
conn = JdbcUtils.getConnection();
//拼接查询SQL语句
String sql = this.generateSql(users);
System.out.println(sql);
//创建PreparedStatement对象
ps = conn.prepareStatement(sql);
//执行sql语句
rs = ps.executeQuery();
while (rs.next()) {
Users user = new Users();
user.setUserid(rs.getInt("userid"));
user.setUsername(rs.getString("username"));
user.setUserage(rs.getInt("userage"));
list.add(user);
}
} catch (Exception e) {
} finally {
JdbcUtils.closeResource(rs, ps, conn);
}
return list;
}
//生成动态条件查询sql
private String generateSql(Users users) {
StringBuffer sb=new StringBuffer("select * from users where 1=1");
if(users.getUserid()>0){
sb.append(" and userid=").append(users.getUserid());
}
if(users.getUsername()!=null && users.getUsername().length()>0){
sb.append(" and username='").append(users.getUsername()).append("'");
}
if(users.getUserage()>0){
sb.append(" and userage=").append(users.getUserage());
}
return sb.toString();
}
public static void main(String[] args) {
DynamicConditionQueryTest dt=new DynamicConditionQueryTest();
Users users=new Users();
users.setUsername("熊大");
users.setUserage(12);
List<Users> list=dt.queryUsers(users);
for(Users user:list){
System.out.println(user.getUserid()+" "+
user.getUsername()+" "+ users.getUserage());
}
}
}
分页查询
分页查询可以避免内存溢出
分页查询简介
当一个操作数据库进行查询的语句返回的结果集内容如果过多,那么内存极有可能溢出,所以在查询中含有大数据的情况下分页是必须的。
分页查询分类:
1.物理分页:
- 在数据库执行查询时(实现分页查询),查询需要的数据—依赖数据库的SQL语句
- 在SQL查询时,从数据库只检索分页需要的数据
- 通常不同的数据库有着不同的物理分页语句
- MySql物理分页采用limit关键字
2.逻辑分页: - 在sql查询时,先从数据库检索出所有数据的结果集,在程序内,通过逻辑语句获得分页需要的数据
如何在MySql中实现物理分页查询
使用limit方式。
limit的使用
select * from tableName limit m,n;
其中m与n为数字。n代表需要获取多少行的数据项,而m代表从哪开始(以0为起始)。
例如我们想从users表中先获取前两条数据SQL为:
select * from users limit 0,2;
那么如果要继续看下两条的数据则为:
select * from users limit 2,2;
*分页公式:(当前页-1)每页大小
创建Page模型
Page
package com.xcm;
import java.util.List;
public class Page <T>{
//当前页
private int currentPage;
//每页显示的条数
private int pageSize;
//总条数
private int totalCount;
//总页数
private int totalPage;
//结果集
private List<T> result;
/**
* 获取
* @return currentPage
*/
public int getCurrentPage() {
return currentPage;
}
/**
* 设置
* @param currentPage
*/
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
/**
* 获取
* @return pageSize
*/
public int getPageSize() {
return pageSize;
}
/**
* 设置
* @param pageSize
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
/**
* 获取
* @return totalCount
*/
public int getTotalCount() {
return totalCount;
}
/**
* 设置
* @param totalCount
*/
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
/**
* 获取
* @return totalPage
*/
public int getTotalPage() {
return totalPage;
}
/**
* 设置
* @param totalPage
*/
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
/**
* 获取
* @return result
*/
public List<T> getResult() {
return result;
}
/**
* 设置
* @param result
*/
public void setResult(List<T> result) {
this.result = result;
}
}
分页查询测试类
//分页查询测试类
public class PageTest {
//分页查询Users
public Page<Users> selectPage(Page page) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Users> list = new ArrayList<>();
try {
//获取连接对象
conn = JdbcUtils.getConnection();
//创建PreparedStatement对象
ps = conn.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()) {
//完成ORM映射
Users users = new Users();
users.setUserid(rs.getInt("userid"));
users.setUsername(rs.getString("username"));
users.setUserage(rs.getInt("userage"));
list.add(users);
}
//将结果集存放到Page对象中
page.setResult(list);
//查询总条数
ps = conn.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, conn);
}
return page;
}
public static void main(String[] args) {
PageTest pt=new PageTest();
Page page=new Page();
page.setCurrentPage(2);
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();
for(Users user:list){
System.out.println(user.getUserid()+" "+user.getUsername()+" "+user.getUserage());
}
}
}
数据库连接池
数据库连接池简介
数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。
**它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。**这项技术能明显提高对数据库操作的性能。
不使用数据库连接池存在的问题
-
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码,所以整个过程比较耗时。
-
需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
-
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
IDBC数据库池必要性;
数据库连接池的基本思想:为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池
负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
数据库连接池在初始化时
将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的优点
- 资源重用: 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
- 更快的系统反应速度: 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。
- 新的资源分配手段: 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置实现某一应用最大可用数据库连接数的限制避免某一应用独占所有的数据库资源.
- 统一的连接管理: 避免数据库连接泄露在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
常用的数据库连接池
- c3p0:是一个开源组织提供的数据库连接池,速度相对较慢,稳定性还可以。
- DBCP:是Apache提供的数据库连接池。速度相对c3p0较快,但自身存在bug。
- Druid:是阿里提供的数据库连接池,据说是集DBCP、c3p0优点于一身的数据库连接池,目前经常使用。
Druid连接池
Druid简介
Druid是阿里提供的数据库连接池,它结合了C3P0、DBCP等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。
Druid使用步骤
1.导入druid-1.2.8.jar包到lib目录下,并引入到项目中
2.在src下创建一个druid.properties类型的文件,并写入
url=jdbc:mysql://localhost:3306/db
driverClassName=com.mysql.jdbc.Driver
username=root
password=1234
initialSize=10
maxActive=20
druid配置信息:
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的url。 | |
username | 连接数据库的用户名。 | |
password | 连接数据库的密码。 | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) |
initialSize | 0 | 初始化时建立物理连接的个数。 |
maxActive | 8 | 最大连接池数量 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
3.加载配置文件
4.获取连接池对象
5.通过连接池对象获取连接
通过数据库连接池获取连接
public class DruidTest {
public static void main(String[] args) throws Exception {
//获取读取druid配置的字节输入流
InputStream is=DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
//创建Properties对象
Properties pos=new Properties();
//加载配置文件
pos.load(is);
//获取连接池对象
DataSource ds= DruidDataSourceFactory.createDataSource(pos);
//获取连接
Connection connection=ds.getConnection();
System.out.println(connection);
}
}
封装工具类
//基于Druid连接池获取数据库连接工具类
public class JdbcDruidUtil {
//数据库连接池对象
private static DataSource dataSource;
static {
try {
//获取读取配置文件的字节输入流对象
InputStream is = JdbcDruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
//创建Properties对象
Properties pop = new Properties();
//加载配置文件
pop.load(is);
//创建连接池对象
dataSource = DruidDataSourceFactory.createDataSource(pop);
}catch (Exception e){
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection()
{
Connection connection=null;
try{
connection=dataSource.getConnection();
}catch (Exception e){
e.printStackTrace();
}
return connection;
}
//关闭连接对象
public static void closeConnection(Connection connection){
try {
connection.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
//提交事务
public static void commit(Connection connection)
{
try {
connection.commit();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
//事务回滚
public static void rollback(Connection connection){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//关闭Statement对象
public static void closeStatement(Statement statement){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//关闭ResultSet
public static void closeResultSet(ResultSet resultSet){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//DML操作时关闭资源
public static void closeResource(Statement statement,Connection connection){
//先关闭Statement对象
closeStatement(statement);
//再关闭Connection对象
closeConnection(connection);
}
//查询时关闭资源
public static void closeResource(ResultSet resultSet,Statement statement,Connection connection){
//先关闭ResultSet
closeResultSet(resultSet);
//再关闭Statement对象
closeStatement(statement);
//最后关闭Connection对象
closeConnection(connection);
}
}
通过druid重构JDBCUtils
1.创建JDBCUtilsDruid类
2.使用静态块初始化连接池
public class JDBCUtilsDruid {
private static DataSource ds=null;
static {
try{
InputStream is=JDBCUtilsDruid.class.getClassLoader().
getResourceAsStream("druid.properties");
Properties properties=new Properties();
properties.load(is);
ds= DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
3.获取数据库连接方法
public static Connection getConnection() throws SQLException {
Connection connection=null;
connection=ds.getConnection();
return connection;
}
4.释放数据库连接方法
public static void close(Connection conn, Statement stmt){
try {
if(stmt!=null){
stmt.close();
}
}catch (SQLException e){
e.printStackTrace();
}finally {
if(conn!=null){
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
应用程序分层
应用程序分层是指通过创建不同的包来实现项目的分层,将项目中的代码根据功能做具体划分,并存放在不同的包下。
三层结构
三层结构就是将整个业务应用划分为:表述层、业务逻辑层 、数据访问层。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。
分层优点
- 分层结构将应用系统划分为若干层,每一层只解决问题的一部分,通过各层的协作提供整体解决方案。大的问题被分解为一系列相对独立的子问题,局部化在每一层中,这样就有效的降低了单个问题的规模和复杂度,实现了复杂系统的第一步也是最为关键的一步分解。
- 分层结构具有良好的可扩展性,为应用系统的演化增长提供了一个灵活的支持,具有良好的可扩展性。增加新的功能时,无须对现有的代码做修改,业务逻辑可以得到最大限度的重用。
- 分层结构易于维护。在对系统进行分解后,不同的功能被封装在不同的层中,层与层之间的耦合显著降低。因此在修改某个层的代码时,只要不涉及层与层之间的接口,就不会对其他层造成严重影响。
分层命名
表述层:web或controller
业务层:service
数据访问层:dao (Data Access Object)
应用程序分层实现
表述层调用业务层,业务层调用数据访问层
在分层项目中实现查询业务
UserDao接口
public interface UserDao {
/**
* 根据用户ID查询用户
*
*/
Users selectUserById(int userid);
/*
修改用户信息
*/
int updateUserById(Users users);
/**
* 根据用户姓名模糊查询
*/
List<Users> selectUsersByLikeName(String username);
}
UserDaoImpl接口实现类
public class UserDaoImpl extends BaseDaoImpl implements UserDao{
/**
* 根据用户ID查询用户
* @param userid
* @return
*/
@Override
public Users selectUserById(int userid) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
Users users=null;
try{
conn= JdbcDruidUtil.getConnection();
ps=conn.prepareStatement("select * from users where userid=?");
ps.setInt(1,userid);
rs=ps.executeQuery();
while (rs.next()){
//收到orm映射
users=new Users();
users.setUserid(rs.getInt("userid"));
users.setUsername(rs.getString("username"));
users.setUserage(rs.getInt("userage"));
}
}catch (Exception e){
e.printStackTrace();
//通过自定义异常解决异常耦合问题
throw new ApplicationException(e.getMessage());
}finally {
JdbcDruidUtil.closeResource(rs,ps,conn);
}
return users;
}
@Override
public int updateUserById(Users users) {
String sql="update users set userage=? where userid=?";
Object[] param=new Object[]
{users.getUserage(),users.getUserid()};
return this.executeUpdate(sql,param);
}
@Override
public List<Users> selectUsersByLikeName(String username) {
String sql="select * from users where username like ?";
Object[] param=new Object[]{"%"+username+"%"};
return this.select(sql,param,Users.class);
}
}
UserService接口
public interface UsersService {
Users findUserById(int userid);
}
UserServiceImpl接口实现类
public class UserServiceImpl implements UsersService {
/**
* 根据用户ID查询用户业务
* @param userid
* @return
*/
@Override
public Users findUserById(int userid) {
UserDao ud=new UserDaoImpl();
return ud.selectUserById(userid);
}
}
web
public class Test {
public static void main(String[] args) {
UsersService us=new UserServiceImpl();
Users u=us.findUserById(1);
System.out.println(u);
}
}
封装通用的BaseDao
封装通用的DML操作
BaseDao接口
//通用接口
public interface BaseDao {
//通用的DML操作方法
int executeUpdate(String sql,Object[] param);
//通用查询方法
//实体类属性名必须与表列名相同
<T> List<T> select(String sql,Object[] param,Class<T> tClass);
}
BaseDaoImpl接口实现类
//通用接口实现类
public class BaseDaoImpl implements BaseDao{
//通用的DML操作方法
@Override
public int executeUpdate(String sql, Object[] param) {
Connection con=null;
PreparedStatement ps=null;
int row;
try{
con= JdbcDruidUtil.getConnection();
ps=con.prepareStatement(sql);
//得到参数的个数
ParameterMetaData pd=ps.getParameterMetaData();
for (int i = 0; i < pd.getParameterCount(); i++) {
ps.setObject(i+1,param[i]);
}
row=ps.executeUpdate();
}catch (Exception e){
e.printStackTrace();
throw new ApplicationException(e.getMessage());
}finally {
JdbcDruidUtil.closeResource(ps,con);
}
return row;
}
@Override
public <T> List<T> select(String sql, Object[] param, Class<T> tClass) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
List<T> list=new ArrayList<>();
try {
conn=JdbcDruidUtil.getConnection();
ps=conn.prepareStatement(sql);
//得到参数的个数
ParameterMetaData pd=ps.getParameterMetaData();
for (int i = 0; i < pd.getParameterCount(); i++) {
ps.setObject(i+1,param[1]);
}
rs=ps.executeQuery();
//获取结果集信息
ResultSetMetaData rm=rs.getMetaData();
while (rs.next()){
//ORM映射
//通过反射实例化实体类对象
T bean=tClass.newInstance();
//实体类的属性名必须要和表的列名相同
for (int i = 0; i < rm.getColumnCount(); i++) {
//得到列名
String columnName= rm.getColumnName(i+1);
//获取列的值
Object value=rs.getObject(columnName);
//通过BeanUtil工具类将值映射到对象中
BeanUtils.setProperty(bean,columnName,value);
}
list.add(bean);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcDruidUtil.closeResource(rs,ps,conn);
}
return list;
}
}
对象的关联关系
关联关系(Association),是一种拥有的关系,它使一个对象知道另一个对象的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。
对象的关联关系解决了什么问题
在多表查询时,使用对象关联关系能够更合理的存放查询到的结果集数据。
** 关联关系的方向性**
- 单向 只在一侧关联了对方。
- 双向 两侧相互关联了对方。
创建对象的关联关系
orders表
CREATE TABLE orders(
orderid int(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
orderprice FLOAT(11,2) DEFAULT NULL,
`user_id` INT(11) DEFAULT NULL,
CONSTRAINT `orders_fk` FOREIGN KEY(`user_id`)
REFERENCES users(userid)
)
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`)
)
orders_itmes表
CREATE TABLE `orders_items`(
`order_id` INT(11) NOT NULL,
`item_id` INT(11) NOT NULL,
PRIMARY KEY(`order_id`,`item_id`),
CONSTRAINT `orders_items_fk` FOREIGN KEY(`item_id`)
REFERENCES `items`(`itemid`),
CONSTRAINT `orders_items_fk1` FOREIGN KEY(`order_id`)
REFERENCES `orders`(`orderid`)
);
对象的关联关系
使用对象关联关系存放查询数据
需求:查询用户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接口实现类
@Override
public Users selectUsers(int userid) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
Users users = new Users();
try {
con = JdbcDruidUtil.getConnection();
ps = con.prepareStatement("SELECT * FROM users u,orders o,orders_items oi,items i\n" +
"WHERE u.userid=o.user_id AND o.orderid=oi.order_id AND\n" +
"oi.item_id=i.itemid 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, con);
}
return users;
}
}
UsersService接口
Users findUsers(int userid);
UsersService接口实现类
@Override
public Users findUsers(int userid) {
UserDao ud=new UserDaoImpl();
return ud.selectUsers(userid);
}