数据持久化 #
数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、XML、二进制流等。
-
数据存储状态一:瞬时状态
- 保存在内存的程序数据,程序退出,数据就消失了
-
数据存储状态二:持久状态
- 保存在磁盘上的程序数据,程序退出后依然存在
数据持久化技术 #
Hibernate、JPA、JDBC(Java Datebase Connectivity)等
JDBC框架 #
Driver 接口 #
java.sql.Driver接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商进行实现,不同数据库厂商提供不同的实现- 在程序中不需要直接去访问实现了 Driver 接口的类,而是由**驱动程序管理器类(
java.sql.DriverManager)**去调用这些Driver实现 - 常见Driver
- Oracle的驱动:
oracle.jdbc.driver.OracleDriver - MySQL的驱动:
com.mysql.jdbc.Driver(mysql6-)、com.mysql.cj.jdbc.Driver(mysql6+)
- Oracle的驱动:
连接、操作数据库步骤 #
Connection conn = null;
Statement st=null;
ResultSet rs = null;
try {
//获得Connection
//创建Statement
//处理查询结果ResultSet
}catch(Exception e){
e.printStackTrance();
} finally {
//释放资源ResultSet,Statement,Connection
}
一、获取数据库连接对象 #
1、导入jar包 #
1、在项目中创建lib文件夹
2、将驱动jar文件放置到lib文件夹
3、集成到项目ClassPath中,右键build(eclipse)、add as library(idea)
2、注册驱动(Java代码中加载驱动类) #
将com.mysql.jdbc包下的Driver类的字节码文件从本地磁盘加载到方法区中
方式一:加载 JDBC 驱动需调用 Class 类的静态方法forName(),向其传递要加载的 JDBC 驱动的类名
//将com.mysql.jdbc包下的Driver类的字节码文件从本地磁盘加载到方法区中
Class.forname("com.mysql.jdbc.Driver")
方式二:DriverManager 类是驱动程序管理器类,负责管理驱动程序(不推荐)
DriverManager.registerDriver(com.mysql.jdbc.Driver);
通常不用显式调用 DriverManager 类的registerDriver() 方法来注册驱动程序类的实例,原因如下:
1、该方法,过于依赖jar包的存在
2、该方法,会造成二次注册
3、使用Class.forname可以降低耦合性
3、获取连接对象 #
//本机IP:localhost 本机端口号:3306
String url = "jdbc:mysql://ip:port/databasename?serverTimezone=Asia/Shanghai&characterEncoding=utf-8";
String user = "username";
String passWord = "password";
Connection conn = DriverManager.getConnection(url,user,passWord);
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为 了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
JDBC URL #
MySQL #
jdbc:mysql://<ip>:<port>/<database_name>?<key>=<value>,常用的连接配置如下
| KEY | VALUE | Description |
|---|---|---|
| serverTimezone | Asia/Shanghai | 时区 |
| useSSL | true、false | 是否使用ssl加密连接 |
| connectTimeout | 10 | 设置连接超时时间,单位是秒 |
| socketTimeout | 30 | 设置读取结果集的超时时间,单位是秒 |
| authenticationPlugins | mysql_native_password、sha256_password | 安全认证方式 |
| characterEncoding | uft8 | 数据库字符集 |
| rewriteBatchedStatements | true、false | 批量执行sql |
| zeroDateTimeBehavior | EXCEPTION默认抛出异常(Zero date value prohibited)、CONVERT_TO_NULL转换为null;ROUND替换成最近的日期即0001-01-01 |
对于零值timestamp类型的处理方式 |
| autoReconnect | true、false | 数据库连接中断是否重连 |
| maxReconnects | 3 | 重新连接尝试次数 |
| initialTimeout | 2 | 两次重连间隔时间,单位是秒 |
| failOverReadOnly | true、false | 是否使用针对数据库连接池的重连策略 |
| useUnicode | true、false | 是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true |
Oracle #
jdbc:oracle:thin:@<ip>:<port>:<database_name>?<key>=<value>
SQLServer #
jdbc:microsoft:sqlserver//<ip>:<port>;DatabaseName=<database_name>
PostgreSQL #
jdbc:postgresql://<ip>:<port>/<database_name>?<key>=<value>
二、执行sql语句 #
1、获取Statement对象 #
Statement statement = conn.createStatement();
2、执行sql语句 #
int result = statement.executeUpdate("sql语句字符串")
Statement类方法分类 #
int executeUpdate(sql)- 针对数据库的增(
insert into)、删(delete from)、改(update set)操作 - 返回值类型:实际影响的行数
- 针对数据库的增(
ResultSet executeQuery(sql)- 针对数据库的查询(
select from)操作 - 返回值类型:一个结果集类型
- 针对数据库的查询(
boolean execute(sql)- 针对数据库的增删改查操作,一般我们不会使用,jdbc的底层代码会使用
- 如果执行的sql语句是增删改,返回false
- 如果执行的sql语句是查询,返回true
3、处理执行结果(ResultSet) #
//使用Statement类的方法executeQuery(String sql);获得结果集类型的对象
ResultSet set = statement.executeQuery(sql);
while(set.next()){
//形参可以直接写字段名,字段名不区分大小写
int id = set.getInt("book_id");
//也可以写字段索引,索引从1开始
int id = set.getInt(1);
}
三、释放资源 #
resultSet.close();
statement.close();
connection.close();
实现JDBC工具类 #
将获取连接和关闭资源等公共、重复的代码封装成一个工具类
import java.sql.*;
public class JDBCUtil {
private static String driver;
private static String url;
private static String user;
private static String passWord;
//解析配置文件.properties
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("jdbc.properties"));
driver = (String) properties.get("driver");
url = (String) properties.get("url");
user = (String) properties.get("user");
passWord = (String) properties.get("passWord");
}catch (Exception e){
e.printStackTrace();
}
}
//获得Connection对象
public static Connection getConnection(){
Connection connection = null;
try{
Class.forName(driver);
connection = DriverManager.getConnection(url,user,passWord);
}catch (Exception e){
e.printStackTrace();
}
return connection;
}
//关闭资源 -- 针对查询
public static void close(ResultSet resultset,Statement statement,Connection connection){
try {
if (resultset != null) {
resultset.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
//关闭资源 -- 针对增删改
public static void close(Statement statement,Connection connection){
close(null,statement,connection);
}
//针对DML语句--增删改
public static boolean executeUpdate(String sql,List<Object> list){
Connection connection = getConnection();
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sql);
for (int i = 0;i < list.size();i++){
pre.setObject(i + 1,list.get(i));
}
return (pre.executeUpdate() > 0)? true : false;
}catch (Exception e){
e.printStackTrace();
}finally {
close(pre,connection);
}
return false;
}
//针对查DQL语句
public static <T> List<T> executeQuery(String sql,List<Object> list,Class<T> tClass){
Connection connection = getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
List<T> li = new ArrayList<>();
try {
statement = connection.prepareStatement(sql);
for (int i = 0;i < list.size();i++){
statement.setObject(i + 1,list.get(i));
}
resultSet = statement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//获取列数
int count = resultSetMetaData.getColumnCount();
//遍历所有行
while (resultSet.next()){
T t = tClass.newInstance();
for (int i = 1;i <= count;i++){
//获取每一列列名
String keyName = resultSetMetaData.getColumnLabel(i);
//获取每一列对应的值
Object value = resultSet.getObject(keyName);
//T中对应的属性
Field key = tClass.getDeclaredField(keyName);
key.setAccessible(true);
key.set(t,value);
}
li.add(t);
}
}catch (Exception e){
e.printStackTrace();
}finally {
close(resultSet,statement,connection);
}
return li;
}
}
封装查询返回值遍历方式 #
List<Map> list = JDBCUtils.executeQuery(sql,new ArrayList());
for (Map<String,Object> map : list){
for (Map.Entry<String,Object> entry : map.entrySet()){
String s = entry.getKey();
Object o = entry.getValue();
System.out.print(s + "=" + o + ",");
}
System.out.println();
}
SQL注入攻击 #
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,如下,从而利用系统的 SQL 引擎完成恶意行为的做法。
SELECT user, password FROM user_table WHERE user='lucy' AND password =
# 用户输入密码为:'abc' OR '1' = '1'
SELECT user, password FROM user_table WHERE user='lucy' AND password = 'abc' OR '1' = '1'
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(继承于Statement) 取代 Statement 就可以了
PreparedStatement类 #
- 可以通过调用 Connection 对象的
preparedStatement()方法获取 PreparedStatement 对象 - PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句,使用
?作为占位符SELECT user, password FROM user_table WHERE user='lucy' AND password = ?
- 使用
setObject(int parameterIndex, Object x),可对指定索引的占位符?进行替换,例如setObject(0,"'abc' OR '1' = '1'")- 最终SQL为
SELECT user, password FROM user_table WHERE user='lucy' AND password = ''abc' OR '1' = '1''
- 最终SQL为
PreparedStatement类和Statement的比较 #
-
PreparedStatement,代码的可读性和可维护性更高
-
PreparedStatement在使用时只需要编译一次,就可以运行多次,Statement每运行一次就编译一次,所以PreparedStatement的效率更高
-
PreparedStatement 可以防止 SQL 注入
-
如果拼接表名、列名、关键字,必须使用Statement,防止sql语句错误
ResultSet类 #
-
通过调用 PreparedStatement 对象的
excuteQuery()方法创建该对象 -
代表结果集
-
ResultSet 返回的实际上就是一张数据表,有一个指针指向数据表的第一条记录的前面。
ResultSetMetaData类 #
- 通过调用ResultSet对象的
getMetaData()方法获取该对象 - 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
常用方法 #
getColumnName(int column):获取指定索引列的名称getColumnLabel(int column):获取指定索引列的别名getColumnCount():返回当前ResultSet列数getColumnTypeName(int column):获取指定索引列的数据库类型名称getColumnDisplaySize(int column):返回指定所有列最大标准宽度,单位字符isNullable(int column):返回指定列中的值是否可以为nullisAutoIncrement(int column):返回指定索引列是否自增
JDBC封装DAO #
**DAO (Data Access objects 数据访问对象)**是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来。能够是代码的结构更加清晰化。
DAO模式组成 #
- DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。
- DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。
- 实体类:用于存放与传输对象数据。
- 数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改