- 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企
业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多
通过各种关系数据库来完成。 - 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数
据文件中。
- 在Java中,数据库存取技术可分为如下几类:
JDBC直接访问数据库
JDO (Java Data Object )技术
第三方O/R工具,如Hibernate, Mybatis 等 - JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操
作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用
这些类库可以以一种标准的方法、方便地访问数据库资源。 - JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使
得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。 - 如果没有JDBC,那么Java程序访问数据库时是这样的:
有了JDBC,Java程序访问数据库时是这样的:
注:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。
使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
注:这里是基于MySql5版本,不适用于MySql8版本。
准备工作:
- 在工程下新建lib文件夹
- mysql-connector-java-5.1.47.jar导入到lib目录下
- 把导入的jar包添加到项目中
- 新建package及测试类
//包名 com.bzcxy.jdbc
//测试类名 Jdbc_Test
- 在数据库中新建baizhan数据库
注:Connection的包为java.sql.Connection
1.1 连接重点Driver接口
- java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用
的,不同数据库厂商提供不同的实现。 - 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类
(java.sql.DriverManager)去调用这些Driver实现。
Oracle的驱动:oracle.jdbc.driver.OracleDriver
MySql的驱动: com.mysql.jdbc.Driver
加载与注册JDBC驱动
- 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动
的类名
Class.forName(“com.mysql.jdbc.Driver”); - 注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用DriverManager.registerDriver()方法来注册自身的一个实例。
URL
- JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,
从而建立到数据库的连接。 - JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:子协议:子名称
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了
定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
MySQL的连接URL编写方式:
- jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
- jdbc:mysql://localhost:3306/baizhan
- jdbc:mysql://localhost:3306/baizhan?useSSL=false(MySql5.7之后,默认开启SSL,通过参数指定连接不使用SSL)
- jdbc:mysql://localhost:3306/baizhan?
useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集) - jdbc:mysql://localhost:3306/baizhan?user=root&password=123456
方式一
public class Jdbc_Test {
public static void main(String[] args) throws SQLException {
//获取driver实现类的对象
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/baizhan?useSSL=false";
//把数据库的用户名和密码封装在Properties中
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","root");
Connection conn = driver.connect(url,info);
System.out.println(conn);
}
}
如果连接失败,会报错
方式二
对方式一的迭代:在如下的程序中不会出现第三方的api,使得程序具有更好的移植性
public class Jdbc_Test2 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//获取driver实现类的对象反射
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.提供要连接的数据库
String url = "jdbc:mysql://localhost:3306/baizhan?useSSL=false";
//3.提供用户密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","root");
//4.获取链接
Connection conn = driver.connect(url,info);
System.out.println(conn);
}
}
方式三
使用DriverManager替换Driver
public class Jdbc_Test3 {
//DriverManager 代替 Driver
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//1.获取Driver的实现类
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2. 提供另外三个获取连接信息
String url = "jdbc:mysql://localhost:3306/baizhan?useSSL=false";
String user = "root";
String password = "root";
//注册到DriverManager
DriverManager.registerDriver(driver);
//获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
}
方式四
MySql驱动默认会进行驱动的注册
public class Jdbc_Test4 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//Driver driver = (Driver) clazz.newInstance();
//注册到DriverManager
// DriverManager.registerDriver(driver);
//1 提供三个获取连接信息
String url = "jdbc:mysql://localhost:3306/baizhan?useSSL=false";
String user = "root";
String password = "root";
//2.加载Driver不用显示注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
}
方式五(推荐)
将数据库链接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式。
在src下创建jdbc.properties文件,并写入
user=root password=root url=jdbc:mysql://localhost:3306/baizhan driverClass=com.mysql.jdbc.Driver
public class Jdbc_Test5 {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//1 读取配置文件中4个基本信息
InputStream is = Jdbc_Test5.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2 加载驱动
Class.forName(driverClass);
//3 获取链接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
}
优点:
- 实现数据与代码的分离 ,实现了解耦
- 如果要修改配置文件信息,可以避免程序重打包
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数
据库连接就是一个Socket连接。
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PreparedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语
句。 - CallableStatement:用于执行 SQL 存储过程。
导入user.sql数据
- 调用Connection对象的createStatement()方法创建Statement对象,
- 调用以下方法执行SQL语句
int execute(String sql); //执行insert、update、delete操作 ResultSet executeQuery(String sql); //执行查询操作
执行insert
public class Statement_Test {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
InputStream is = Jdbc_Test5.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url,user,password);
//获取statement对象
Statement statement = conn.createStatement();
Scanner sc = new Scanner(System.in);
System.out.println("输入账号");
String username = sc.next();
System.out.println("输入密码");
String userpassword = sc.next();
//拼接sql
String sql = "insert into user(username,userpassword) values('"
+ username + "','" + userpassword + "')";
//执行sql
statement.execute(sql);
//关闭资源
conn.close();
statement.close();
}
}
2.3 ResultSet
- ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。next 方法将指针移
动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使
用它来迭代结果集。 - 默认的 ResultSet 对象仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到
最后一行的顺序进行。 - ResultSet 接口提供用于从当前t行检索列值的获取方法(getBoolean、getLong 等)。可以使用列
的索引编号或列的名称检索值。一般情况下,使用列索引较为高效。列从 1 开始编号。为了获得最
大的可移植性,应该按从左到右的顺序读取每行中的结果集列,而且每列只能读取一次。
ResultSet内存结构
执行select
public class Statement_Test2 {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
InputStream is = Jdbc_Test5.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url,user,password);
//获取Statement
Statement statement = conn.createStatement();
String sql = "select * from user";
//执行sql查询,并将结果集返回
ResultSet rs = statement.executeQuery(sql);
//遍历rs获取所有结果
while (rs.next())
{
System.out.println(rs.getString("username") + "_"
+ rs.getString("userpassword"));
}
conn.close();
//关闭资源
statement.close();
rs.close();
}
}
使用Statement的弊端:
- 存在字符串拼接操作,繁琐。
- 存在SQL注入问题。
- PreparedStatement接口继承Statement
- PreparedStatement是预编译的,比Statement速度快
- 调用Connection对象的preparedStatement(String sql)方法获取PreparedStatement对象
- 调用以下方法执行SQL语句
int executeUpdate(); //执行insert、update、delete操作 Resultset executeQuery(); //执行查询操作3.1 PreparedStatement添加
- 创建PreparedStatement对象
String sql = "insert into user(username,userpassword) values(?,?)"; PreparedStatement ps = conn.prepareStatement(sql);
PreparedStatement对象所代表的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对
象的setXxx()方法来设置这些参数。
- 填充占位符
ps.setString(1,"admin6"); ps.setString(2,"123456");
setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是
设置的SQL语句中的参数的值。
注:
- 目前是设置String,所以使用setString方法。
- setString方法会自动给参数携带引号,该语句执行过程
insert into user(username,userpassword) values('admin6','123456')
- 执行添加
ps.executeUpdate();
- 关闭资源
ps.close(); conn.close();
public class PreparedStatement_Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
InputStream is = Jdbc_Test5.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url,user,password);
//获取PreparedStatement
String sql = "insert into user(username,userpassword) values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"admin6");
ps.setString(2,"123456");
ps.executeUpdate();
ps.close();
conn.close();
}
}
3.2 JDBCUtils封装数据库连接与释放
为了统一对Connection资源的管理及使用,创建JDBCUtils工具类,实现对数据库的连接与释放进行统一管理。
数据库连接
public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException {
//1 读取配置文件中4个基本信息
InputStream is = Jdbc_Test5.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2 加载驱动
Class.forName(driverClass);
//3 获取链接
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
释放连接:
public static void close(Connection conn, Statement statement) throws SQLException {
if(conn != null)
{
conn.close();
}
if (statement != null)
{
statement.close();
}
}
3.3 PreparedStatement修改
- 获取连接并创建PreparedStatement对象
Connection conn = JDBCUtils.getConnection(); String sql = "update user set username = ? where id = ?"; PreparedStatement ps = conn.prepareStatement(sql);
- 填充占位符
ps.setObject(1,"admintest"); ps.setInt(2,1);
使用setObject设置username,用setInt设置id
- 执行修改
ps.executeUpdate();
- 关闭资源
JDBCUtils.close(conn,ps);3.4 PreparedStatement通用增删改
sql中占位符的个数与可变形参的长度相同
//通用的增删改操作
public static void update(String sql,Object...args) throws Exception {
//1、获取数据库的连接
Connection conn = JDBCUtils.getConnection();
//2、预编译sql语句,返回PrepareStatement的实例
PreparedStatement ps = conn.prepareStatement(sql);
//3、填充占位符
for (int i = 0; i < args.length; i ++)
{
ps.setObject(i + 1,args[i]);
}
//4、执行
ps.executeUpdate();
//5、资源的关闭
JDBCUtils.close(conn,ps);
}
使用通用方法删除id为1和2的数据
String sql = "delete from user where id = ? or id = ?"; update(sql,1,2);3.5 PreparedStatement查询
Connection conn = JDBCUtils.getConnection();
String sql = "select * from user";
PreparedStatement ps = conn.prepareStatement(sql);
//executeQuery()查询结果集
ResultSet rs = ps.executeQuery();
//遍历数据
while (rs.next())
{
System.out.println(rs.getString("username") + "_" +
rs.getString("userpassword"));
}
rs.close();
JDBCUtils.close(conn,ps);
简单的通用查询设计
public static void query(String sql,Object...args) throws SQLException, IOException, ClassNotFoundException {
//1、获取数据库的连接
Connection conn = JDBCUtils.getConnection();
//2、预编译sql语句,返回PrepareStatement的实例
PreparedStatement ps = conn.prepareStatement(sql);
//3、填充占位符
for (int i = 0; i < args.length; i ++)
{
ps.setObject(i + 1,args[i]);
}
//4、执行
ResultSet rs = ps.executeQuery();
//获取ResultSet元数据//获取结果集元数据
ResultSetmetaData rsmd = rs.getmetaData();
//通过ResultSetmetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
while (rs.next())
{
//根据列数,通过rs.getObject方法+索引获取列的值
for (int i = 0; i < columnCount;i++ )
{
System.out.print(rs.getObject(i + 1 ) + " ");
}
System.out.println();
}
//5、资源的关闭
rs.close();
JDBCUtils.close(conn,ps);
}
4 ORM编程思想
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping)
- 一个数据表对应一个JAVA类
- 表中的一条记录对应JAVA类的一个对象
- 表中的一个字段(列)对应JAVA类的一个属性(字段)
Java典型的ORM框架中有 - hibernate:全自动的框架,强大、复杂、笨重、学习成本较高
- Mybatis:半自动的框架(懂数据库的人 才能操作) 必须要自己写sql
- JPA:JPA全称Java Persistence API、JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,是Java
自带的框架
针对user表通用查询设计
根据user表设计实体类User
package com.bzcxy.jdbc;
public class User {
private Integer id;
private String username;
private String userpassword;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword;
}
}
package com.bzcxy.jdbc;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class PreparedStatement_Test4 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException, IOException {
String sql = "select * from user where id < ?";
List users = queryUser(sql,6);
System.out.println();
}
public static List queryUser(String sql,Object...args) throws SQLException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//1、获取数据库的连接
Connection conn = JDBCUtils.getConnection();
//2、预编译sql语句,返回PrepareStatement的实例
PreparedStatement ps = conn.prepareStatement(sql);
//3、填充占位符
for (int i = 0; i < args.length; i ++)
{
ps.setObject(i + 1,args[i]);
}
List users = new ArrayList<>();
//4、执行
ResultSet rs = ps.executeQuery();
//获取结果集元数据
ResultSetmetaData rsmd = rs.getmetaData();
//通过ResultSetmetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
while (rs.next())
{
//创建User对象,用于保存每行数据
User u = new User();
//处理结果集中的每一行的列,将每列的值保存到user对象中对应的属性中
for(int i = 0; i < columnCount;i ++)
{
//获取该列的值
Object columnValue = rs.getObject(i + 1);
//获取该列的名称
String columnName = rsmd.getColumnName(i + 1);
//通过反射给user对象的columnName字段赋值为columnValue
//获取该字段相关属性
Field field = u.getClass().getDeclaredField(columnName);
//提升权限
field.setAccessible(true);
//赋值
field.set(u,columnValue);
}
//将赋值后的user对象,添加进list
users.add(u);
}
rs.close();
//5、资源的关闭
JDBCUtils.close(conn,ps);
return users;
}
}
三、用户登录功能
1 业务介绍
1.1 业务需求
模拟用户登录功能实现。
1.2 业务描述程序运行的时候,提供一个输入的入口,可以让用户输入账号和密码,用户输入之后,提交信息,JAVA
程序收集到用户信息,连接数据库验证用户名和密码是否合法。
合法:显示登录成功
不合法:显示登录失败
需要提供一张Account数据表,其中包含用户id、用户账号、用户密码、用户昵称。
导入account.sql
初始化方法
public static MapinitUI() { Scanner sc = new Scanner(System.in); System.out.println("请输入账号"); String account = sc.next(); System.out.println("请输入密码"); String password = sc.next(); Map userLoginInfo = new HashMap<>(); userLoginInfo.put("account",account); userLoginInfo.put("password",password); return userLoginInfo; }
测试界面
public static void main(String[] args) {
initUI();
}
3 登录实现
既然要访问数据库,将JDBCUtils引入该项目
Account实体类
package com.bzcxy.login;
public class Account {
private Integer userid;
private String useraccount;
private String username;
private String userpassword;
public String getUseraccount() {
return useraccount;
}
public void setUseraccount(String useraccount) {
this.useraccount = useraccount;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword;
}
}
针对account表实现通用查询
public static ListqueryAccount(String sql,Object...args) throws SQLException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { List accounts = new ArrayList<>(); //1、获取数据库的连接 Connection conn = JDBCUtils.getConnection(); //2、预编译sql语句,返回PrepareStatement的实例 PreparedStatement ps = conn.prepareStatement(sql); //3、填充占位符 for (int i = 0; i < args.length; i ++) { ps.setObject(i + 1,args[i]);//小心参数声明错误 } //4、执行 ResultSet rs = ps.executeQuery(); //获取结果集元数据 ResultSetmetaData rsmd = rs.getmetaData(); //通过ResultSetmetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); while (rs.next()) { Account account = new Account(); for (int i = 0;i < columnCount; i ++) { //获取该列的值 Object columnValue = rs.getObject(i + 1); //获取该列的名称 String columnName = rsmd.getColumnName(i + 1); //获取该字段相关属性 Field field = account.getClass().getDeclaredField(columnName); //提升权限 field.setAccessible(true); //注入列值//赋值 field.set(account,columnValue); } accounts.add(account); } rs.close(); //5、资源的关闭 JDBCUtils.close(conn,ps); return accounts; }
登录实现
public static boolean login(MapuserLoginInfo) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException, IOException { //定义sql String sql = "select * from account where useraccount = ? and userpassword = ?"; //获取所有匹配账号密码的account对象 List accounts = queryAccount(sql,userLoginInfo.get("account"),userLoginInfo.get("password")); //如果该集合为0,代表账号或密码没有匹配,登录失败 if (accounts.size() == 0) { return false; } return true; }
测试登录功能
package com.bzcxy.login;
import com.bzcxy.jdbc.JDBCUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
public class Login {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException, IllegalAccessException, NoSuchFieldException {
Map userLoginInfo = initUI();
System.out.println(login(userLoginInfo)?"登录成功":"登录失败");
}
public static boolean login(Map userLoginInfo) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException, IOException {
String sql = "select * from account where useraccount = ? and userpassword = ?";
List accounts = queryAccount(sql,userLoginInfo.get("account"),userLoginInfo.get("password"));
if (accounts.size() == 0)
{
return false;
}
return true;
}
public static Map initUI()
{
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号");
String account = sc.next();
System.out.println("请输入密码");
String password = sc.next();
Map userLoginInfo = new HashMap<>();
userLoginInfo.put("account",account);
userLoginInfo.put("password",password);
return userLoginInfo;
}
public static List queryAccount(String sql,Object...args) throws SQLException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
List accounts = new ArrayList<>();
Connection conn = JDBCUtils.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i ++)
{
ps.setObject(i + 1,args[i]);
}
ResultSet rs = ps.executeQuery();
ResultSetmetaData rsmd = rs.getmetaData();
int columnCount = rsmd.getColumnCount();
while (rs.next())
{
Account account = new Account();
for (int i = 0;i < columnCount; i ++)
{
//获取列值
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = rsmd.getColumnName(i + 1);
//获取属性信息
Field field = account.getClass().getDeclaredField(columnName);
//提升权限
field.setAccessible(true);
//注入列值
field.set(account,columnValue);
}
accounts.add(account);
}
rs.close();
JDBCUtils.close(conn,ps);
return accounts;
}
}
四、JDBC高级
1 SQL注入介绍
SQL注入是指利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的
SQL语句段或者命令,从而利用系统的SQL引擎完成恶意行为的做法。
修改account表通用查询,使用Statement实现
public static ListqueryAccount(String sql, Object...args) throws SQLException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { List accounts = new ArrayList<>(); //获取数据库的连接 Connection conn = JDBCUtils.getConnection(); Statement statement = conn.createStatement(); //执行 ResultSet rs = statement.executeQuery(sql); //获取结果集元数据 ResultSetmetaData rsmd = rs.getmetaData(); //通过ResultSetmetaData获取结果集中的列数 int columncount = rsmd.getColumnCount(); while (rs.next()) { //创建User对象,用于保存每行数据 Account account = new Account(); //处理结果集中的每一行的列,将每列的值保存到user对象中对应的属性中 for (int i = 0; i < columncount; i ++) { //获取该列的值 Object columnValue = rs.getObject(i + 1); //获取该列的名称 String columnName = rsmd.getColumnName(i + 1); //通过反射给user对象的columnName字段赋值为columnValue //获取该字段相关属性 Field field = account.getClass().getDeclaredField(columnName); //提升权限 field.setAccessible(true); //赋值 field.set(account,columnValue); } accounts.add(account); } //资源的关闭 JDBCUtils.close(conn, statement); return accounts; }
修改登录实现代码
public static boolean login(MapuserLoginInfo) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException, IOException { //定义sql,拼接参数 String sql = "select * from account where useraccount = '" + userLoginInfo.get("account") + "' and userpassword = '" + userLoginInfo.get("password") + "'"; //select * from account where useraccount = 'zhangsan' and userpassword = 'baizhan'or'1=1' //获取所有匹配账号密码的account对象 List accounts = queryAccount(sql); //如果该集合为0,代表账号或密码没有匹配,登录失败 if (accounts.size() == 0) { return false; } return true; }
测试用例正常
以上输入的账号和密码,通过SQL拼接,在执行过程中的SQL实际上是:
select * from account where useraccount = 'zhangsan' and userpassword = 'baizhan'or'1=1'
由于1=1永远成立,所以不论账号密码是否正确,都会返回。
导致SQL注入的根本原因:
用户输入的信息中包含SQL语句的关键字,并且这些关键字参与SQL语句的编译过程,导致SQL语句的原
意被扭曲,进而达到SQL注入的目的。
只要用户提供的信息不参与SQL语句的编译过程,即使用户提供的信息中包含SQL语句的关键字,但是
没有参与编译,仍然不起作用。
PreparedStatement可以将信息参数化,仍然用PreparedStatement实现登录功能:
public static ListqueryAccount(String sql,Object...args) throws SQLException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { List accounts = new ArrayList<>(); //1、获取数据库的连接 Connection conn = JDBCUtils.getConnection(); //2、预编译sql语句,返回PrepareStatement的实例 PreparedStatement ps = conn.prepareStatement(sql); //3、填充占位符 for (int i = 0; i < args.length; i ++) { ps.setObject(i + 1,args[i]);//小心参数声明错误 } //4、执行 ResultSet rs = ps.executeQuery(); //获取结果集元数据 ResultSetmetaData rsmd = rs.getmetaData(); //通过ResultSetmetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); while (rs.next()) { //创建User对象,用于保存每行数据 Account account = new Account(); //处理结果集中的每一行的列,将每列的值保存到user对象中对应的属性中 for (int i = 0;i < columnCount; i ++) { //获取列值 Object columnValue = rs.getObject(i + 1); //获取列名 String columnName = rsmd.getColumnName(i + 1); //获取属性信息 Field field = account.getClass().getDeclaredField(columnName); //提升权限 field.setAccessible(true); //注入列值 field.set(account,columnValue); } accounts.add(account); } rs.close(); //5、资源的关闭 JDBCUtils.close(conn,ps); return accounts; }
登录实现
public static boolean login(MapuserLoginInfo) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException, IOException { //定义sql String sql = "select * from account where useraccount = ? and userpassword = ?"; //获取所有匹配账号密码的account对象 List accounts = queryAccount(sql,userLoginInfo.get("account"),userLoginInfo.get("password")); //如果该集合为0,代表账号或密码没有匹配,登录失败 if (accounts.size() == 0) { return false; } return true; }
结果:
以上输入的账号和密码,通过PreparedStatement预编译,将baizhan’or’1=1作为一个整体的字符串参
数设置到SQL当中,在执行过程中的SQL实际上是:
select * from account where useraccount = 'zhangsan' and userpassword = "baizhan'or'1=1"3 批量插入数据
update、delete本身就具有批处理操作的效果。
创建一张物品空表
Create table goods( id int PRIMARY key auto_increment, goodsname VARCHAR(25) )
向goods表中插入2000条数据
1. 通过Statement + for循环方式批量插入数据,计算执行时间
public class Insert_Test {
public static void main(String[] args) throws Exception {
Connection conn = JDBCUtils.getConnection();
Statement statement = conn.createStatement();
//获取起始时间
Long start = System.currentTimeMillis();
for(int i = 0; i < 2000; i ++)
{
String sql = "insert into goods(goodsname)values('name_" + i + "')";
statement.execute(sql);
}
//获取结束时间
Long end = System.currentTimeMillis();
JDBCUtils.close(conn,statement);
System.out.println("插入时间为:" + (end - start));
}
}
由于方法一使用的是statement,所以每次需要重新生成sql字符串。
2. 通过PreparedStatement + for循环方式批量插入数据,计算执行时间
public class Insert_Test2 {
public static void main(String[] args) throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "insert into goods(goodsname)values(?)";
PreparedStatement psmt = conn.prepareStatement(sql);
Long start = System.currentTimeMillis();
for(int i = 0; i < 2000; i ++)
{
psmt.setObject(1,"name_" + i);
psmt.executeUpdate();
}
Long end = System.currentTimeMillis();
JDBCUtils.close(conn,psmt);
System.out.println("插入时间为:" + (end - start));
}
}
方法二使用的是PreparedStatement,PreparedStatement是预编译模式,DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数
直接传入就可以执行。
3. 通过PreparedStatement的addBatch()和executeBatch()进行批量插入数据
- addBatch()把若干SQL语句装载到一起,然后一次性传送到数据库执行,即是批量处理sql数
据的。 - executeBatch()会将装载到一起的SQL语句执行。
- clearBatch()清除缓存
注:MySql默认情况下是不支持批处理的
但从5.1.13开始添加了一个rewriteBatchStatement的参数,让MySql支持批处理。
在加载url时设置该参数:rewriteBatchedStatements=true
url=jdbc:mysql://localhost:3306/baizhan? userSSL=false&rewriteBatchedStatements=true
public class Insert_Test3 {
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Connection conn = JDBCUtils.getConnection();
String sql = "insert into goods(goodsname)values(?)";
PreparedStatement psmt = conn.prepareStatement(sql);
Long start = System.currentTimeMillis();
for(int i = 0; i < 2000; i ++)
{
psmt.setObject(1,"name_" + i);
缓存sql
psmt.addBatch();
//每500条缓存执行一次
if (i % 500 == 0)
{
//一次性将500条sql全部执行
psmt.executeBatch();
//清空缓存
psmt.clearBatch();
}
}
Long end = System.currentTimeMillis();
JDBCUtils.close(conn,psmt);
System.out.println("插入时间为:" + (end - start));
}
}
4 Blob类型数据
4.1 MySql Blob类型
- MySql中,Blob是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
- 插入Blob类型的数据必须使用PreparedStatement,因为Blob类型的数据无法使用字符串拼接。
- MySql中有四种Blob类型,它们除了在存储的最大容量上不同,其他是一致的。
- 实际使用中根据需要存入的数据大小定义不同的Blob类型。
- 需要注意的是:如果存储的文件过大,数据库的性能会下降。
- 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在MySql的安装目录下,找到
my.ini文件加上如下配置参数:max_allowed_packet=16M。并重启MySql服务。
- 导入movieactor.sql
photo列类型为MediumBlob,存储照片的二进制。 - 通过PreparedStatement存储Blob类型数据
//第二个参数传入流 setBlob(int parameterIndex, InputStream inputStream)
public class Blob_Test {
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Connection conn = JDBCUtils.getConnection();
String sql = "insert into movieactor(actorname,photo)values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"朱茵");
//读入图片流
InputStream is = new FileInputStream(new File("G:/actorimg/zhuyin.jpg"));
ps.setBlob(2,is);
ps.executeUpdate();
JDBCUtils.close(conn,ps);
}
}
防乱码
user=root password=root url=jdbc:mysql://localhost:3306/baizhan?useSSL=false&rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8 driverClass=com.mysql.jdbc.Driver
可以在Navicat中直接查看
选择图像之后:
public class Blob_Test2 {
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Connection conn = JDBCUtils.getConnection();
String sql = "select * from movieactor where photo != ''";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next())
{
int id = rs.getInt("id");
String name = rs.getString("actorname");
//获取Blob类型数据,保存成jpg格式文件
Blob blob = rs.getBlob("photo");
InputStream is = blob.getBinaryStream();
FileOutputStream fos = new FileOutputStream(id + "_" + name + ".jpg");
byte[] Buffer = new byte[1024];
int len;
while ( (len = is.read(Buffer)) != -1)
{
fos.write(Buffer,0,len);
}
is.close();
fos.close();
}
JDBCUtils.close(conn,ps);
}
}
4.4 特殊情况
读入一张7M左右的图片
public class Blob_Test3 {
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Connection conn = JDBCUtils.getConnection();
String sql = "insert into movieactor(actorname,photo)values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"照片");
//读入7M左右的文件
InputStream is = new FileInputStream(new File("G:/actorimg/photo.jpg"));
ps.setBlob(2,is);
ps.executeUpdate();
JDBCUtils.close(conn,ps);
}
}
报错
Exception in thread "main" com.mysql.jdbc.PacketTooBigException: Packet for query is too large (8056024 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.
虽然MediumBlob允许保存最大值为16M,但MySql默认允许值为4194304即4M
在my.ini中添加max_allowed_packet=16M,并重启MySql服务。
再次执行,即可存入数据。
- 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要不同时成功,要不同时失败。
- 事务操作:
- 开启事务
- 提交事务
- 回滚事务
- 在JDBC中,使用Connection对象来管理事务
- 开启事务:void setAutoCommit(boolean autoCommit):调用该方法设置参数为false时,代
表开启事务。 - 提交事务:commit()
- 回滚事务:rollback()
- 开启事务:void setAutoCommit(boolean autoCommit):调用该方法设置参数为false时,代
银行转账业务:
非事务实现:
- 加载相关数据,导入bank.sql
- 由zhangsan向lisi转账500
public class Bank_Test {
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement psmt = null;
try{
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql = "update bank set balance = balance + ? where accountname = ?";
psmt = conn.prepareStatement(sql);
//由zhangsan向lisi转账,所以lisi账户增加500
psmt.setObject(1,500);
psmt.setObject(2,"lisi");
psmt.executeUpdate();
//zhansan账户减500
psmt.setObject(1,-500);
psmt.setObject(2,"zhangsan");
psmt.executeUpdate();
}catch (Exception ex)
{
System.out.println(ex.getMessage());
}
finally {
//关闭所有资源
JDBCUtils.close(conn,psmt);
}
}
}
- 如果在两个executeUpdate()之间发生异常,则部分转账正常执行
模拟异常
psmt.setObject(1,500); psmt.setObject(2,"lisi"); psmt.executeUpdate(); //模拟异常 //int i = 1 / 0; psmt.setObject(1,-500); psmt.setObject(2,"zhangsan"); psmt.executeUpdate();
- 由于两次SQL操作之间出现异常,所以只有部分转账成功
严重错误!lisi账户增加,zhangsan未减少。
事务实现:
在获取数据库连接之后,通过Connection对象开启事务
由zhangsan向lisi转账500
public class Bank_Test {
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement psmt = null;
try{
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
String sql = "update bank set balance = balance + ? where accountname = ?";
psmt = conn.prepareStatement(sql);
//由zhangsan向lisi转账,所以lisi账户增加500
psmt.setObject(1,500);
psmt.setObject(2,"lisi");
psmt.executeUpdate();
psmt.setObject(1,-500);
psmt.setObject(2,"zhangsan");
psmt.executeUpdate();
//如果没出错,提交事务
conn.commit();
}catch (Exception ex)
{
//如果出错,回滚事务
conn.rollback();
System.out.println(ex.getMessage());
}
finally {
//不管是否正常提交事务,均关闭资源
//关闭所有资源
JDBCUtils.close(conn,psmt);
}
}
}
模拟异常
psmt.setObject(1,500); psmt.setObject(2,"lisi"); psmt.executeUpdate(); //模拟异常 int i = 1 / 0; psmt.setObject(1,-500); psmt.setObject(2,"zhangsan"); psmt.executeUpdate();
再次执行,由于有异常,所以所有操作回滚,达到多操作的原子性效果。
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序中建立数据库连接
- 进行sql操作
- 断开数据库连接
- 这种模式开发,存在的问题
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。
- 需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想:为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
- 资源重用:由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
- 更快的系统反应速度:数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。
- 新的资源分配手段:对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置实现某一应用最大可用数据库连接数的限制避免某一应用独占所有的数据库资源.
- 统一的连接管理:避免数据库连接泄露在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
- c3p0是一个开源组织提供的数据库连接池,速度相对较慢,稳定性还可以。
- DBCP是Apache提供的数据库连接池。tomcat服务器自带dbcp数据库连接池。速度相对c3p0较 快,但自身存在bug。
- Proxool是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性不如
c3p0。 - Druid是阿里提供的数据库连接池,据说是集DBCP、c3p0、proxool优点于一身的数据库连接池,目前经常使用。
- 导入c3p0-0.9.5.2.jar包到lib目录下,并引入到项目中,c3p0依赖mysql-connector-java包。
- 导入mchange-commons-java-0.2.12.jar包,c3p0数据库连接池的辅助包。这是更新c3p0-0.9.2版本后分离出来的包,0.9.1的时候还是只是一个包
- 从c3p0连接池中获取Connection连接
public class C3P0_Test {
public static void main(String[] args) throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource();
//配置JDBC相关数据cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/baizhan");
cpds.setUser("root");
cpds.setPassword("root");
//设置初始化时,数据库连接池的连接数量
cpds.setInitialPoolSize(10);
//获取连接
Connection conn = cpds.getConnection();
System.out.println(conn);
}
}
- 可以通过DataSources销毁连接池
void destroy(DataSource pooledDataSource)
DataSources.destroy(cpds);2.2 c3p0配置xml使用方式
- 在src目录下添加名为c3p0-config.xml文件。
- 在c3p0-config.xml配置内容如下:
com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/baizhan root root 5 10 10 100 200 2
- 从c3p0连接池中获取Connection连接
public class C3P0_Test2 {
public static void main(String[] args) throws SQLException {
//使用默认的数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource("localc3p0");
Connection conn = cpds.getConnection();
System.out.println(conn);
}
}
- 也可以在c3p0-config.xml设置多个数据库连接池,根据连接池名字不同,来初始化不同的连接池。
如下的配置中,有两个数据库连接池配置
com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/baizhan root root 5 10 10 100 200 2 com.mysql.jdbc.Driver jdbc:mysql://xxx.xxx.xxx.xxx:3306/bjsxt root root
- 通过named-config中的name属性的值,来初始化不同的数据库连接池
//获取名为localc3p0连接池
ComboPooledDataSource cpds = new ComboPooledDataSource("localc3p0");
Connection conn = cpds.getConnection();
System.out.println(conn);
3 DBCP数据库连接池
- DBCP 是 Apache 软件基金组织下的开源连接池实现
- Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也
可由应用程序独立使用。
DBCP配置信息:
- 导入commons-dbcp2-2.5.0.jar包及其依赖包commons-pool2-2.6.0.jar、commons-logging-
1.2.jar到lib目录下,并引入项目中 - 从dbcp连接池中获取Connection连接
public class DBCP_Test {
public static void main(String[] args) throws SQLException {
BasicDataSource source = new BasicDataSource();
//配置JDBC相关数据
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/baizhan");
source.setUsername("root");
source.setPassword("root");
//设置初始化时,数据库连接池的连接数量
source.setInitialSize(10);
//获取数据库连接
Connection conn = source.getConnection();
System.out.println(conn);
}
}
3.2 DBCP配置文件使用方式
- 在src下创建一个dbcp.properties类型的文件,并写入
url=jdbc:mysql://localhost:3306/baizhan driverClassName=com.mysql.jdbc.Driver username=root password=root initialSize=10 maxActive=20
- 加载配置文件
InputStream is = DBCPTest2.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties properties = new Properties();
properties.load(is);
- 获取连接池对象,通过该连接池对象获取连接
DataSource ds = BasicDataSourceFactory.createDataSource(properties); Connection conn = ds.getConnection(); System.out.println(conn);
public class DBCP_Test2 {
public static void main(String[] args) throws Exception {
InputStream is = DBCP_Test2.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties properties = new Properties();
properties.load(is);
DataSource ds = BasicDataSourceFactory.createDataSource(properties);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
4 druid连接池(德鲁伊)
druid是阿里提供的数据库连接池,据说是集DBCP、c3p0、proxool优点于一身的数据库连接池,目前
经常使用。
- 导入druid-1.0.19.jar包到lib目录下,并引入到项目中
- 在src下创建一个druid.properties类型的文件,并写入
url=jdbc:mysql://localhost:3306/baizhan?useSSL=false driverClassName=com.mysql.jdbc.Driver username=root password=root initialSize=10 maxActive=20
druid配置信息:
- 加载配置文件
InputStream is = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
pros.load(is);
- 获取连接池对象,通过该连接池对象获取连接
//获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(properties); //获取连接 Connection conn = ds.getConnection(); System.out.println(conn);
public class Druid_Test {
public static void main(String[] args) throws Exception {
InputStream is = Druid_Test.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
4.2 通过druid重构JDBCUtils
- 创建JDBCUtilsDruid类
- 使用静态块初始化连接池
public class JDBCUtilsDruid {
private static DataSource ds = null;
static {
InputStream is = Druid_Test.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
try {
properties.load(is);
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
- 获取数据库连接方法
public static Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();
return conn;
}
- 释放数据库连接方法
public static void close(Connection conn, Statement statement) throws SQLException {
if(conn != null)
{
conn.close();
}
if (statement != null)
{
statement.close();
}
}
七、JDBC_DAO模式
1 应用程序分层
应用程序通过创建不同的包来实现项目的分层,将项目中的代码根据功能做具体划分,并存放在不同的包下。
com.bzcxy.controller//控制层(连接前端、界面层、UI和后端的中间层) com.bzcxy.service//业务逻辑层 (登录,具体业务逻辑) com.bzcxy.dao//数据库操作 (JDBC) com.bzcxy.pojo//数据库实体类(数据库的实体类)1.1 分层优点
- 分层结构将应用系统划分为若干层,每一层只解决问题的一部分,通过各层的协作提供整体解决方案。大的问题被分解为一系列相对独立的子问题,局部化在每一层中,这样就有效的降低了单个问题的规模和复杂度,实现了复杂系统的第一步也是最为关键的一步分解。
- 分层结构具有良好的可扩展性,为应用系统的演化增长提供了一个灵活的支持,具有良好的可扩展性。增加新的功能时,无须对现有的代码做修改,业务逻辑可以得到最大限度的重用。
- 分层架构易于维护。在对系统进行分解后,不同的功能被封装在不同的层中,层与层之间的耦合显著降低。因此在修改某个层的代码时,只要不涉及层与层之间的接口,就不会对其他层造成严重影响。
三层结构就是将整个业务应用划分为:
界面层(User Interface layer):提供与用户交互的界面,如GUI(图形用户界面),web页面等;
业务逻辑层(Business Logic Layer):负责各种业务逻辑,直接访问数据库,提供对业务数据的保
存、更新、删除和查询操作;
数据访问层(Data access layer):负责存放管理应用的持久性业务数据
三层结构的特点是:
所有下层向上层提供调用的接口,具体实现细节对上层透明。层与层之间存在自上而下的依赖关系,即上层会访问下层的API,但下层不依赖于上层。
DAO:Data Access Object访问数据库信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete)、而不包含任何业务相关的信息。
2.1 通用baseDAO实现DAO一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记
录等方法的底层数据操作自定义类。
由于可能操作多个数据库表,这样就需要为每个表提供一个操作他的类 xxDAO, 这些DAO继承baseDAO 就可以省略很多重复代码(从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的代码)。
- 实现一个通用的baseDao抽象类
package com.bzcxy.dao; import com.bzcxy.jdbc.JDBCUtilsDruid; import org.apache.commons.beanutils.BeanUtils; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetmetaData; import java.util.ArrayList; import java.util.List; public abstract class baseDao{ //定义一个变量来接受泛型的类型 private Class type; //获取T的Class对象,获取泛型类型,泛型是在被子类继承时才确定 public baseDao() { //获取子类类型 Class clazz = this.getClass(); //获取父类类型 // getGenericSuperclass()用来获取当前类的父类的类型 // ParameterizedType表示的是带泛型的类型 ParameterizedType parameterizedType = (ParameterizedType)clazz.getGenericSuperclass(); //获取具体的泛型类型 //getActualTypeArguments获取具体的泛型类型 //这个方法返回一个Type类型数组 Type[] types = parameterizedType.getActualTypeArguments(); //获取具体的泛型类型 this.type = (Class )types[0]; } }
- 通用的增删改操作
//通用增删改操作
public int excuteQuery(String sql,Object...param) throws Exception
{
//获取连接
Connection conn = JDBCUtilsDruid.getConnection();
PreparedStatement psmt = conn.prepareStatement(sql);
//绑定参数
for (int i = 0; i < param.length; i ++)
{
psmt.setObject(i + 1,param[i]);
}
int rows = psmt.executeUpdate();
JDBCUtilsDruid.close(conn,psmt);
//返回受影响的行数
return rows;
}
- 通用的查询操作,需要用到commons-beanutils-1.9.3.jar包及其依赖包commons-logging-1.2.jar,将它们导入并引入工程
//通用查询方法,返回零条或多条查询记录
public List getBean(String sql,Object...param)throws Exception
{
List list = new ArrayList<>();
//获取连接
Connection conn = JDBCUtilsDruid.getConnection();
PreparedStatement psmt = conn.prepareStatement(sql);
//绑定参数
for (int i = 0; i < param.length; i ++)
{
psmt.setObject(i + 1,param[i]);
}
ResultSet rs = psmt.executeQuery();
//获取结果集元数据
ResultSetmetaData rsmd = rs.getmetaData();
while (rs.next())
{
T bean = type.newInstance();
for (int i = 0; i < rsmd.getColumnCount(); i ++)
{
//获得列名
String comlumnName = rsmd.getColumnName(i + 1);
//获取列值
Object value = rs.getObject(comlumnName);
//通过BeanUtil工具将类注入到对象中
BeanUtils.setProperty(bean,comlumnName,value);
}
list.add(bean);
}
return list;
}
- 获取单一值,如select(*)
//获取单一值,如select(*) select max(date)等等
public Object getValue(String sql,Object...param) throws Exception
{
Object res = null;
Connection conn = JDBCUtilsDruid.getConnection();
PreparedStatement psmt = conn.prepareStatement(sql);
//绑定参数
for (int i = 0; i < param.length; i ++)
{
psmt.setObject(i + 1,param[i]);
}
ResultSet rs = psmt.executeQuery();
if(rs.next())
{
//获取第一列的值
res = rs.getObject(1);
}
return res;
}
2.2 UserDAO
- 创建UserDAO接口
public interface UserDAO {
//根据username获取一条记录
User getUser(String username) throws Exception;
//插入一条User
void insertUser(User user) throws Exception;
//根据id删除一条数据
void deleteUserById(Integer id) throws Exception;
//获取一共有多少用户
Integer getUserCount() throws Exception;
}
- 创建UserDAO的实现类UserDAOImplUserDAOImpl继承baseDAO由于每个方法都要抛出异常,所以在接口UserDAO的方法后面也会throw异常
package com.bzcxy.dao; import com.bzcxy.jdbc.User; import java.util.List; public class UserDAOImpl extends baseDaoimplements UserDAO { @Override public User getUser(String username) throws Exception { User u = null; String sql = "select * from user where username = ?"; //使用baseDAO中的通用查询方法getBean List list = this.getBean(sql,username); //判断是否有值 if(list.size() != 0) { u = list.get(0); } return u; } @Override public void insertUser(User user) throws Exception { String sql = "insert into user(username,userpassword)values(?,?)"; //使用baseDAO中的通用增删改方法 this.excuteQuery(sql,user.getUsername(),user.getUserpassword()); } @Override public void deleteUserById(Integer id) throws Exception { String sql = "delete from user where id = ?"; //使用baseDAO中的通用增删改方法 this.excuteQuery(sql,id); } @Override public Integer getUserCount() throws Exception { String sql = "select count(*) from user"; //使用baseDAO中获取单一值的方法 Integer count = Integer.valueOf(this.getValue(sql).toString()) ; return count; } }
- 测试各个方法
public class Main {
public static void main(String[] args) throws Exception {
UserDAO userDAO = new UserDAOImpl();
//测试insertuser
// User u = new User();
// u.setUsername("test");
// u.setUserpassword("password");
// userDAO.insertUser(u);
//测试get
// User u = userDAO.getUser("test");
// System.out.println(u.getUsername() + ":" + u.getUserpassword());
//测试delete
userDAO.deleteUserById(7);
//获取用户总数
System.out.println(userDAO.getUserCount());
}
}
八、DbUtils工具
1 Apache-DbUtils
- commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
- API介绍:
org.apache.commons.dbutils.QueryRunner
org.apache.commons.dbutils.ResultSetHandler
工具类:org.apache.commons.dbutils.DbUtils
QueryRunner类是Dbutils的核心类之一,提供对sql语句操作的API
- int update(String sql, Object… params):执行insert update delete操作
- query(String sql, ResultSetHandler rsh, Object… params) :执行 select操作
- 导入commons-dbutils-1.6.jar包,并引入工程
- 通过DataSource对象初始化QueryRunner对象
public class DBUtils_Test {
public static void main(String[] args) throws Exception {
InputStream is = DBUtils_Test.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
//通过数据库连接池初始化QueryRunner对象
QueryRunner queryRunner = new QueryRunner(DruidDataSourceFactory.createDataSource(properties));
}
}
- 编写sql,并通过update方法执行
String sql = "insert into user(username,userpassword)values(?,?)";
//count代表影响了几行记录
int count = queryRunner.update(sql,"QueryRunnerTest","QueryRunner");
System.out.println(count);
注:实际上QueryRunner的update方法仍然使用PreparedStatement来实现。
3 ResultSetHandlerResultSetHandler接口(org.apache.commons.dbutils.ResultSethandler)执行处理一个结果集对象,将数据转变并处理为任何一种形式,供其他应用使用。
public interface ResultSetHandler{ T handle(ResultSet var1) throws SQLException; }
ResultSetHandler实现类
返回单条记录并封装成实体类对象
public class DBUtils_Test2 {
public static void main(String[] args) throws Exception {
InputStream is = DBUtils_Test2.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
QueryRunner queryRunner = new QueryRunner(DruidDataSourceFactory.createDataSource(properties));
String sql = "select * from user where id = ?";
//一条记录使用BeanHandler
//给BeanHandler提供相关实体类信息
BeanHandler bh = new BeanHandler<>(User.class);
//使用实体类对象直接接收数据
//query参数,分别为sql语句,ResultSetHandler对象,及sql语句中的占位符参数
User user = queryRunner.query(sql,bh,7);
System.out.println(user.getUsername());
}
}
返回多条记录并封装成实体类对象列表
public class DBUtils_Test3 {
public static void main(String[] args) throws Exception {
InputStream is = DBUtils_Test3.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
QueryRunner queryRunner = new QueryRunner(DruidDataSourceFactory.createDataSource(properties));
String sql = "select * from user";
//多条记录使用BeanListHandler
//给BeanListHandler提供相关实体类信息
BeanListHandler bh = new BeanListHandler<>(User.class);
//使用实体类集合直接接收所有数据
//query参数,分别为sql语句,ResultSetHandler对象
List users = queryRunner.query(sql,bh);
for(User u : users)
{
System.out.println(u.getUsername());
}
}
}
3.2 通过QueryRunner查询特殊数据
查询User表中一共有多少条数据
public class DBUtils_Test4 {
public static void main(String[] args) throws Exception {
InputStream is = DBUtils_Test4.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
QueryRunner queryRunner = new QueryRunner(DruidDataSourceFactory.createDataSource(properties));
String sql = "select count(*) from user";
//通过ScalarHandler保存单一值
ScalarHandler sh = new ScalarHandler();
Long count = (Long)queryRunner.query(sql,sh);
System.out.println(count);
}
}
3.3 自定义ResultSetHandler
实现ResultSetHandler接口,返回一个Map对象:
- 创建MyResultSetHandler类,并implements ResultSetHandler接口,重写handle方法
public class MyResultSetHandler implements ResultSetHandler {
@Override
public Object handle(ResultSet resultSet) throws SQLException {
return null;
}
}
- 当QueryRunner从数据库中查询完毕,会调用handle方法,并传入一个ResultSet参数,通过
ResultSet参数封装Map返回。
public class MyResultSetHandler implements ResultSetHandler {
@Override
public Object handle(ResultSet resultSet) throws SQLException {
Map map = null;
if(resultSet.next())
{
//创建一个map对象
map = new HashMap<>();
map.put("id",resultSet.getString("id"));
map.put("username",resultSet.getString("username"));
map.put("userpassword",resultSet.getString("userpassword"));
}
return map;
}
}
- 通过MyResultSetHandler获取map对象
public class MyResultSetHandler implements ResultSetHandler {
@Override
public Object handle(ResultSet resultSet) throws SQLException {
Map map = null;
if(resultSet.next())
{
map = new HashMap<>();
map.put("id",resultSet.getString("id"));
map.put("username",resultSet.getString("username"));
map.put("userpassword",resultSet.getString("userpassword"));
}
return map;
}
public static void main(String[] args) throws Exception {
InputStream is = DBUtils_Test.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(is);
QueryRunner queryRunner = new QueryRunner(DruidDataSourceFactory.createDataSource(properties));
String sql = "select * from user where id = ?";
MyResultSetHandler msh = new MyResultSetHandler();
Map map = (Map)queryRunner.query(sql,msh,7);
System.out.println(map);
}
}
九、JAVA与Oracle
在Oracle中创建表orcl_user
create table orcl_user( id number(11) primary key, username varchar(255), userpassword varchar(255) );
执行插入语句
insert into orcl_user values(1,'admin1','123456'); insert into orcl_user values(2,'admin2','456789'); insert into orcl_user values(3,'admin3','aaabbb'); insert into orcl_user values(4,'admin4','cccddd'); commit;
注:Oracle默认不自动提交,需要执行commit提交,否则数据并未真正添加
Oracle的连接URL编写方式:
- jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
- jdbc:oracle:thin:@localhost:1521:orcl
Oracle的驱动:oracle.jdbc.driver.OracleDriver
- 导入ojdbc6.jar,并引入工程
- 加载驱动及获取连接
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String user = "system";
String password = "root";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
- 通过PreparedStatement查询数据
public class JDBCOracle_Test {
public static void main(String[] args) throws Exception {
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String user = "system";
String password = "root";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url,user,password);
// System.out.println(conn);
String sql = "select * from orcl_user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next())
{
System.out.println(
rs.getString("id") + ":" +
rs.getString("username") + ":" +
rs.getString("userpassword")
);
}
conn.close();
rs.close();
ps.close();
}
}
十、JDBC分页技术
1 分页概述
当一个操作数据库进行查询的语句返回的结果集内容如果过多,那么内存极有可能溢出,所以在大数据的情况下分页是必须的。
分页技术实现:
物理分页:
- 在数据库执行查询时(实现分页查询),查询需要的数据—依赖数据库的SQL语句
- 在SQL查询时,从数据库只检索分页需要的数据
- 通常不同的数据库有着不同的物理分页语句
- MySql/Oracle,每种数据库的分页写法是不同的
- MySql物理分页采用limit关键字,Oracle物理分页采用rowNum
逻辑分页:
- 在sql查询时,先从数据库检索出所有数据的结果集,在程序内,通过逻辑语句获得分页需要的数据
向MySql的user表中添加4条数据
insert into user values(5,'admin5','def'); insert into user values(6,'admin6','ghi'); insert into user values(7,'admin7','jkl'); insert into user values(8,'admin8','opq');
Limit的使用
select * from user limit m,n
其中m与n为数字。n代表需要获取多少行的数据项,而m代表从哪开始(以0为起始)。
例如我们想从user表中先获取前两行数据项(1-2)的name列数据,则SQL为:
select * from user limit 0,2;
那么如果要继续往下看一页两行的数据项(3-4)则下一步的SQL应该为:
select * from user limit 2,2;
以此类推
分页公式:(当前页-1)*每页大小
向Oracle的orcl_user表中添加4条数据
insert into orcl_user values(5,'admin5','eeefff'); insert into orcl_user values(6,'admin6','ggghhh'); insert into orcl_user values(7,'admin7','iiijjj'); insert into orcl_user values(8,'admin8','kkklll'); commit;
注:Oracle默认不自动提交,需要执行commit提交,否则数据并未真正添加
rownum是对结果集加的一个伪列,即先查到结果集之后再加上去的一个列 (先要有结果集)。
简单的说 rownum 是对符合条件结果的序列号。它总是从1开始排起的。
所以通过rownum进行分页查询,必须先附加rownum。
rownum使用
select orcl_user.*,rownum from orcl_user
结果集为
通过rownum列来实现分页
例如我们想从orcl_user表中先获取前两行数据项(1-2)的name列数据,则SQL为:
select ou.* from (select orcl_user.*,rownum rn from orcl_user) ou where rn > 0 and rn <= 2
那么如果要继续往下看一页两行的数据项(3-4)则下一步的SQL应该为:
select ou.* from (select orcl_user.*,rownum rn from orcl_user) ou where rn > 2 and rn <= 4
以此类推
分页公式:
第一个r n:(当前页-1)*每页大小
第二个r n:当前页*每页大小
jdbc通过分页关键字实现分页效果,将分页结果存在分页对象中
分页类
package com.bzcxy.page;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Page {
//当前页数
private Integer currPage;
//每页显示的记录数
private Integer pageSize;
//总记录数
private Integer totalCount;
//总页数
private Integer totalPage;
//每页显示的数据
private List
3.1 MySql实现分页查询
分页方法实现
package com.bzcxy.page;
import com.bzcxy.jdbc.JDBCUtilsDruid;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
public class MySqlPage {
public static void main(String[] args) throws Exception {
Page page = new Page();
//设置当前查询页数
page.setCurrPage(2);
//每页大小
page.setPageSize(2);
Page respage = selectPage(page);
//输出总页数
System.out.println(respage.getTotalPage());
//输出总记录数
System.out.println(respage.getTotalCount());
//输出结果集
System.out.println(respage.getList());
}
public static Page selectPage(Page page) throws Exception {
Connection conn = JDBCUtilsDruid.getConnection();
String sql = "select * from user limit ?,?";
//预处理sql
PreparedStatement ps = conn.prepareStatement(sql);
//设置查询页数 (当前页-1)*每页数量
ps.setInt(1,(page.getCurrPage() - 1) * page.getPageSize());
//设置每页数量
ps.setInt(2,page.getPageSize());
//执行sql
ResultSet rs = ps.executeQuery();
//获取元数据
ResultSetmetaData rsmd = rs.getmetaData();
while (rs.next())
{
Map map = new HashMap<>();
//根据元数据填充map
for (int i = 0; i < rsmd.getColumnCount(); i ++)
{
String columnName = rsmd.getColumnName(i + 1);
String columnValue = rs.getString(i + 1);
map.put(columnName,columnValue);
}
page.getList().add(map);
}
//查询总记录数
sql = "select count(*) from user";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
if (rs.next())
{
//获取总记录数
int count = rs.getInt(1);
//设置总记录数
page.setTotalCount(count);
//总页数=总数/每页数量 向上取整
Double totalpage = Math.ceil((double) count / (double) page.getPageSize());
page.setTotalPage(totalpage.intValue());
}
return page;
}
}
分页测试
Page page = new Page(); //当前查询页数 page.setCurrPage(2); //每页大小 page.setPageSize(2); Page respage = selectPage(page); //输出总页数 System.out.println(respage.getTotalPage()); //输出总记录数 System.out.println(respage.getTotalCount()); //输出结果集 System.out.println(respage.getList());3.2 Oracle的分页实现
package com.bzcxy.page;
import com.bzcxy.jdbc.JDBCUtilsDruid;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
public class OraclePage {
public static void main(String[] args) throws Exception {
Page page = new Page();
//设置当前查询页数
page.setCurrPage(2);
//每页大小
page.setPageSize(2);
Page respage = selectPage(page);
//输出总页数
System.out.println(respage.getTotalPage());
//输出总记录数
System.out.println(respage.getTotalCount());
//输出结果集
System.out.println(respage.getList());
}
public static Page selectPage(Page page) throws Exception {
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String user = "system";
String password = "root";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url,user,password);
String sql = "select ou.* from (select orcl_user.* , rownum rn from orcl_user) ou where rn > ? and rn <= ?";
//预处理sql
PreparedStatement ps = conn.prepareStatement(sql);
//第一个rn (当前页-1)*每页数量
ps.setInt(1,(page.getCurrPage() - 1) * page.getPageSize());
//第二个rn 当前页*每页数量
ps.setInt(2,page.getPageSize() * page.getCurrPage());
//执行sql
ResultSet rs = ps.executeQuery();
//获取元数据
ResultSetmetaData rsmd = rs.getmetaData();
while (rs.next())
{
Map map = new HashMap<>();
//根据元数据填充map
for (int i = 0; i < rsmd.getColumnCount(); i ++)
{
String columnName = rsmd.getColumnName(i + 1);
String columnValue = rs.getString(i + 1);
map.put(columnName,columnValue);
}
page.getList().add(map);
}
//查询总记录数
sql = "select count(*) from orcl_user";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
if (rs.next())
{
//获取总记录数
int count = rs.getInt(1);
//设置总记录数
page.setTotalCount(count);
//总页数=总数/每页数量 向上取整
Double totalpage = Math.ceil((double) count / (double) page.getPageSize());
page.setTotalPage(totalpage.intValue());
}
return page;
}
}
分页测试
Page page = new Page(); //当前查询页数 page.setCurrPage(2); //每页大小 page.setPageSize(2); Page respage = selectPage(page); //输出总页数 System.out.println(respage.getTotalPage()); //输出总记录数 System.out.println(respage.getTotalCount()); //输出结果集 System.out.println(respage.getList());
网页编程与设计:HTML5



