数据持久化:把数据永久的存储到磁盘中。
数据库厂商不同(mysql/oracle/sqlServer),内核也不同。 java应用程序提供了接口规范【jdbc】,数据库厂商针对这一套接口实现[驱动就是一大堆实现类]。
一、JDBC
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要 面向这套接口编程即可。不同的数据库厂商,需要针对这套接口, 提供不同实现。不同的实现的集合,即为不同数据库的驱动。————面向接口编程
JDBC是一个独立于特定数据库管理系统通用的SQL数据库存取和操作的公共接口
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统 ?
jdbc:
- 面向应用的api;
- 面向数据库的api:java Driver Api,给厂商提供的
jdbc驱动程序分类:
- odbc
- jdbc-odbc
- 部分本地api部分Java的驱动程序
- 本地协议的纯 Java 驱动程序
二、java程序连接mysql四种方式
不管哪种方式有俩个一定一样的点:
1、连接字符串 ? ? ?ip地址和端口号 ? ? ?数据库实例名(连接哪一个数据库) 2、用户名和密码
Driver接口 ?java.sql.Driver ?是所有 JDBC 驱动程序需要实现的接口 不同数据库厂商提供这个接口不同的实现类
方式一:关键字,Properties
每次用户名和密码,都要创建 Properties ,有一点点麻烦
@Test
public void test1() throws Exception {
//不同的数据库厂商的driver实现类不同,所以这里定义为null,用反射动态获取Driver对象
Driver driver = null;
//1. 获取驱动 这里mysql5和mysql是不一样的,这里用的是MySQL8
String className = "com.mysql.cj.jdbc.Driver";
//不确定的对象用反射
Class clazz = Class.forName(className);
//获取Driver实例
driver = (Driver) clazz.newInstance();
//2. 获取连接 这里mysql5和mysql8不一样,这里用的是MySQL8 myemployees是数据库实例名
String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
Properties info = new Properties();
//Properties集合的key是user和password,固定死的不可以改
info.setProperty("user", "root");
info.setProperty("password", "123456");
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
方式二:关键字,DriverManager? 驱动管理类
DriverManager.registerDriver 注册驱动
DriverManager.getConnection(url, user, password); 获取连接,不再封装properties
@Test
public void test2() throws Exception {
String className = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
String user = "root";
String password = "123456";
//1. 注册驱动
Class clazz = Class.forName(className);
Driver driver = (Driver) clazz.newInstance();
DriverManager.registerDriver(driver);
//2. 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
?方式三:关键字,加载驱动不再是注册驱动
已经在静态代码块中注册完成了,在类加载的时候就会注册驱动了
@Test
public void test3() throws Exception {
String className = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
String user = "root";
String password = "123456";
//1.加载驱动
Class.forName(className);
//2. 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
方式四:结合属性文件properties
Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例
?
@Test
public void test4() throws Exception {
Properties props = new Properties();
props.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties"));
String driverClassName = props.getProperty("driverClassName");
String url = props.getProperty("url");
String user = props.getProperty("user");
String password = props.getProperty("password");
//1. 加载驱动,注册驱动的事已经在Driver中帮我们做了
Class.forName(driverClassName);
//2. 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
三、JDBCUtils?
自己封装一个类,这样方便连接数据库
- 1.获取数据库的连接? ? JDBCUtils .getConnection()
- 2.关闭资源? ?? JDBCUtils.close()
public class JDBCUtils {
/**
* 获取数据库连接
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
Properties props = new Properties();
//jdbc.properties在类路径下,即src下
props.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
String driverClassName = props.getProperty("driverClassName");
String url = props.getProperty("url");
String user = props.getProperty("user");
String password = props.getProperty("password");
//1. 加载驱动
Class.forName(driverClassName);
//2. 获取连接
return DriverManager.getConnection(url, user, password);
}
/**
* 关闭连接
* @param conn
* @param ps
* @param rs
*/
public static void close(Connection conn, PreparedStatement ps, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 关闭连接
* @param conn
* @param ps
*/
public static void close(Connection conn, PreparedStatement ps){
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
四、Statement接口 和 PreparedStatement接口
俩者都可以发送sql语句给数据库,但是PreparedStatement接口可以预编译sql,且可以防止sql注入。
面试题:为什么不用Statement接口
1、Statement 要用+拼接字符串的方式拼接sql,麻烦且容易出错。 2.? 会造成sql注入,比如:1' or 1='1? ,因为是拼接字符串,会造成sql永远成立
-- 正常的使用用户名、密码 登录
SELECT USER, PASSWORD FROM user_table
WHERE USER = 'AA' AND PASSWORD = '123456'
-- 传入的用户名和密码 1' or 1='1 也可以登录成功,拼接sql存在sql注入,应用占位符方式
SELECT USER, PASSWORD FROM user_table
WHERE USER = '1' OR 1='1' AND PASSWORD = '1' OR 1='1'
?
PreparedStatement接口
使用占位符解决了sql注入,预编译sql:就是提前放到内存了。
每个数据库都有自己的内存:【mysql内存情况】 查询缓存:存储sql语句,不经常改变的sql 语句【???】使用占位符的预编译sql也会放在这里 数据缓冲区: 日志缓冲区:恢复备份
sql->查询缓存-》解析-》校验->生成执行计划-》执行【待完善、、、】
PreparedStatement接口 完成增删改
写一个通用的方法update(),达到使用java程序就能执行增删改数据表数据的效果
? ? @Test
? ? public void test3() {
? ? ? ? String sql = "insert into `order`(order_name, order_date) values(?,?)";
//后俩个参数是占位符的值,注意顺序是对应的
? ? ? ? int row = update(sql, "JJ", "1999-9-9");
? ? ? ? System.out.println("已影响" + row + "行");
? ? }
这里异常会比较多,可以先不管,最后写完代码后,?close关闭资源代码留在外面,其他选中。ctrl+alt+t? ? 【?try一下】
public int update(String sql, Object... args)
这里sql即我们预编译的sql,是有?的。args是可变参数,用来传递占位符?要给的值。
public class JDBCTest {
public int update(String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
int row = 0;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.通过当前连接获取PreparedStatement实例ps,参数要一个String类型的sql语句
ps = conn.prepareStatement(sql);
// 3.填充占位符。ps.setXXX(); sql中索引从1开始。 ps.setDate() 是java.sql.Date
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 4.发送sql给数据库,ps.excuteUpdate();//返回受影响的行数
row = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.关闭连接 用工具类 JDBCUtils .close(conn,ps);
JDBCUtils.close(conn, ps);
}
return row;
}
}
五、ORM 对象关系映射
- 数据库中的一张表对应java一个类,
- 数据表中的一条数据对应Java一个对象?
查询单个对象get()---基础版本
? ? @Test
? ? public void testGet(){
? ? ? ? Customer cus = get();
? ? ? ? System.out.println(cus);
? ? }
ResultSet 结果集
next()? //返回true和false,返回true移动记录指针到下一行 getXXX()? ?//根据列的索引,从1开始 getXXX()//有别名按照别名取,没有别名按照列名取
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
}
public Customer get(){
Connection conn = null;
PreparedStatement ps = null;
Customer customer = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select id, name, email, birth from customers where id = ?";
ps = conn.prepareStatement(sql);
customer = null;
ps.setInt(1, 16);
// ResultSet接口:执行查询后生成的数据表
// 返回ResultSet 结果集
rs = ps.executeQuery();
// next() 返回true和false,并移动记录指针到下一行,开始时记录指针在第一行之前
// 查多行时使用while while(rs.next())
if(rs.next()){
// 重载方法:
// getXXX() 根据列的索引获取,从1开始
// getXXX() 有别名按照别名取,没有别名按照列名取
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
// ORM 对象关系映射
// 数据库中的一张表对应java一个类,
// 数据表中的一条数据对应Java一个对象 如果是属性Date类型要是java.sql下的
customer = new Customer(id, name, email, birth);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 结果集也要关闭,jdbcutils加一个重载方法close
JDBCUtils.close(conn, ps, rs);
}
return customer;
}
泛型与反射优化
编写一个通用的查询,适用于任何表
从数据库中查到的数据封装到一个对象或对象list
优化的方向
1. 返回值类型处 ? ? ①不确定的类型使用泛型 ? ? ②不确定的对象使用反射
2. 结果集的处理? ? ? ? ResultSetMetaData : 描述结果集的元数据
ResultSetMetaData【元数据】
可用于获取关于 ResultSet 结果集中列的类型和属性信息
ResultSetMetaData:可以获取对应的 ResultSet 有多少列, 每一列的列名都是什么。
- ? ? ? getColumnCount() : 获取结果集的列的个数
- ? ? ? getColumnName() : 获取结果集列名
- ? ? ? getColumnLabel() : 获取结果集列的别名,有别名获取别名,没别名 ?getColumnName()
testQuery()和query()是我自己默写出来的,自豪
public class Order {
private int orderId;//对应数据库order表的order_id
private String orderName;//对应数据库表的order_name
private Date orderDate;//对应数据库表的order_date
}
@Test
public void testQuery() throws Exception {
//查询数据库表customers,把获取到的数据封装到Customer对象
String sql1 = "select id, name, email, birth from customers where id = ?";
Customer cust = query(Customer.class, sql1, 18);
System.out.println(cust);
//查询数据库表order,order是关键字,用``。把获取到的数据封装到Order对象
//并且数据库中列名和Order类的属性名不一致,要起给查到的结果集取别名才行
String sql2 = "select order_id orderId, order_name orderName, order_date orderDate from `order` where order_id = ?";
Order order = query(Order.class, sql2, 1);
System.out.println(order);
}
public <T> T query(Class<T> clazz, String sql, Object... args) throws Exception {
//初始化查询到的结果值(调用无参构造器)
T t = clazz.newInstance();
Connection conn = JDBCUtils.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//向数据库发送sql,获取结果集
ResultSet rs = ps.executeQuery();
//拿到描述当前结果集的元数据metadata
ResultSetMetaData rsmd = rs.getMetaData();
//给要返回的T对象封装从结果集种拿到的值
//移动记录指针
while (rs.next()) {
//在当前行上遍历列,获取一个一个结果集表中的单元格值
for (int i = 0; i < rsmd.getColumnCount(); i++) {
//获取列别名
String columnName = rsmd.getColumnLabel(i + 1);
//获取此行此列的单元格里的值
Object columnValue = rs.getObject(columnName);
//给T对象属性赋值。此时没有Set、get方法,用反射赋值
//必须使结果集列的别名与对象属性名保持一致!!!!
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
}
//从下到上关闭资源
JDBCUtils.close(conn, ps, rs);
return t;
}
运行结果:
当然,我们还可以queryList()获取一堆Customer对象,而不仅仅是一个
@Test
public void testQueryList() throws Exception {
// Date date = new Date(System.currentTimeMillis());
// System.out.println(date); 2022-05-11
String sql = "select id, name, email, birth from customers where id <= ?";
List<Customer> customers = queryList(Customer.class, sql, 20);
for (Customer customer : customers) {
System.out.println(customer);
}
}
public <T> List<T> queryList(Class<T> clazz, String sql, Object... args) throws Exception {
List<T> list = new ArrayList<>();
//1. 获取连接
Connection conn = JDBCUtils.getConnection();
//2. 获取 PreparedStatement 用于发送 SQL
PreparedStatement ps = conn.prepareStatement(sql);
//3. 填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4. 执行 SQL,获取 ResultSet
ResultSet rs = ps.executeQuery();
//5. 获取当前结果集的元数据 ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
//6. 通过结果集元数据获取,结果集的列数
int columnCount = rsmd.getColumnCount();
//7. 获取结果集中的数据
while (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//7.1 根据列的索引获取列别名
String columnName = rsmd.getColumnLabel(i + 1);
//7.2 根据列名获取列值
Object columnValue = rs.getObject(columnName);
//用开源组织的类,给对象设置属性值,此处需要添加commons-beanutils-1.8.0.jar包。我们也可以自己用反射写
BeanUtils.setProperty(t, columnName, columnValue);
}
list.add(t);
}
//8. 关闭连接
JDBCUtils.close(conn, ps, rs);
return list;
}
运行结果:
六、使用?PreparedStatement 完成图片处理
mysql中的数据类型:BLOB
BLOB | 二进制形式的长文本数据,最大可达4G | TEXT? | 长文本数据,最大可达4G |
customers表的表结构如图:
使用?PreparedStatement 完成图片添加到数据库
ps.setBlob(5, new FileInputStream("./aligeduo.jpg"));
@Test
//添加图片
public void test1(){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConnection();
String sql = "insert into customers values(?,?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, 21);
ps.setString(2, "Ali嘎多");
ps.setString(3, "aligeduo@qq.com");
ps.setString(4, "2001-9-9");
//使用perparestatement 完成图片处理
//void setBlob(int parameterIndex, InputStream inputStream)
ps.setBlob(5, new FileInputStream("./aligeduo.jpg"));
int row = ps.executeUpdate();
System.out.println("已影响" + row + "行");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, ps, null);
}
}
数据库中就可以看得到了,看到我们美丽的Ali嘎多了
使用?PreparedStatement 从数据库中取出图片
ResultSet rs = ps.executeQuery();
Blob blob = rs.getBlob("photo");
//获取图片输入流
InputStream in = blob.getBinaryStream();
并把图片保存到硬盘。
//查询图片
@Test
public void test2() throws Exception {
Connection conn = JDBCUtils.getConnection();
//多查询一个列,图片
String sql = "select id, name, email, birth, photo from customers where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 21);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer cust = new Customer(id, name, email, birth);
System.out.println(cust);
//图片处理,获取数据库图片以Blob形式,Blob??
//将二进制大对象 (Binary Large Object) 存储为数据库表某一行中的一个列
Blob blob = rs.getBlob("photo");
//获取图片输入流
InputStream in = blob.getBinaryStream();
FileOutputStream fos = new FileOutputStream("./lalala.jpg");
//将查到的图片保存到磁盘中
byte[] b = new byte[1024];
int len = 0;
while ((len = in.read(b)) != -1) {
fos.write(b, 0, len);
}
//关闭流和连接
fos.close();
in.close();
JDBCUtils.close(conn, ps, rs);
}
}
七、开启批量处理 要注意mysql版本号
创建一个空表:emp。
?假设我们要添加10万条数据到这个表,
1、下面这种就很慢,超级无敌变态慢
等了2分钟,才进去12000多条数据,等不下去了
@Test
public void test1(){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConnection();
String sql = "insert into emp values(?,?)";
ps = conn.prepareStatement(sql);
for (int i = 0; i <= 100000; i++) {
ps.setInt(1, i+1);
ps.setString(2, "emp_" + i);
//填充一条,就给远程数据库发送一条
ps.executeUpdate();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, ps, null);
}
}
2、真正的批处理
当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理
- addBatch(String):添加需要批量处理的SQL语句或是参数;
- executeBatch():执行批量处理语句;
- clearBatch():清空缓存的数据
在这之前,需要在jdbc.properties中开启批处理
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&rewriteBatchedStatements=true
user=root
password=123456
?6s,就能往emp中插入10万条数据,快吗?
//批量处理
@Test
public void test3() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "insert into emp values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
//这里使用批处理往emp表中插入10万条数据
for (int i = 1; i <= 100000; i++) {
ps.setInt(1, i+1);
ps.setString(2, "emp_" + i);
//积攒 SQL 语句
ps.addBatch();
if(i % 500 == 0){
//批量发送sql
ps.executeBatch();
//清空sql
ps.clearBatch();
}
}
JDBCUtils.close(conn, ps, null);
}
|