22-05-11 西安 jdbc(01)java连接数据库、Statement、PreparedStatement、图片的处理、批处理、把查询结果封装对象
前言:
数据持久化:把数据永久的存储到磁盘中。
java应用程序提供了接口规范【jdbc】,数据库厂商针(mysql/oracle/sqlServer)对这一套接口实现 [驱动就是一大堆实现类]。
JDBC
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程
Driver接口 java.sql.Driver 是所有 JDBC 驱动程序需要实现的接口
不同数据库厂商提供这个接口不同的实现类
JDBC是一个独立于特定数据库管理系统通用的SQL数据库存取和操作的公共接口
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统
jdbc:
- 面向应用的api;
- 面向数据库的api:java Driver Api,给厂商提供的
jdbc驱动程序分类:
- odbc
- jdbc-odbc
- 部分本地api部分Java的驱动程序
- 本地协议的纯 java 驱动程序
java程序连接mysql四种方式
直接看方式四,前面的都是演进过程!,方式四也会封装为 JDBCUtils .getConnection(),来获取数据库连接。
方式一:关键字,Properties
每次用户名和密码,都要创建 Properties ,有一点点麻烦
@Testpublic void test1() throws Exception {//不同的数据库厂商的driver实现类不同,所以这里定义为null,用反射动态获取Driver对象Driver driver = null;//1. 获取驱动 这里mysql5和mysql是不一样的,这里用的是MySQL8String 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
@Testpublic 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() 方法来注册自身的一个实例

@Testpublic 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();}}}
}
PreparedStatement接口
防止sql注入
俩者都可以发送sql语句给数据库,但是PreparedStatement接口可以预编译sql,且可以防止sql注入。
面试题:为什么不用Statement接口
1、Statement 要用+拼接字符串的方式拼接sql,麻烦且容易出错。
2. 会造成sql注入,比如:1' or 1='1 ,因为是拼接字符串,会造成sql永远成立所谓SQL注入,指的是通过把SQL命令插入到Web表单提交或者输入域名或者页面请求的查询字符串, 最终达到欺骗服务器,达到执行恶意SQL命令的目的。
-- 传入的用户名和密码 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内存了。
Statement 和 PreparedStatement之间的关系和区别.
1.prepareStatement()会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用预编译后的结果。可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。
2.安全性更高,没有 SQL 注入的隐患。
3.提高了程序的可读性
通用的增删改update()
目的:写一个通用的方法update(),达到使用java程序就能执行增删改数据表数据的效果
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.Datefor (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;}
public int update(String sql, Object... args) 使用举例:
@Testpublic void test3() {String sql = "insert into `order`(order_name, order_date) values(?,?)";//后俩个参数是占位符的值,注意顺序是对应的int row = update(sql, "JJ", "1999-9-9");System.out.println("已影响" + row + "行");}
这里sql即我们预编译的sql,是有?的。args是可变参数,用来传递占位符?要给的值。
查询与更新
- PreparedStatement 接口中的方法 描述 int executeUpdate() 执行 DML,增删改的操作,返回影响的行数。
- ResultSet executeQuery() 执行 DQL,查询的操作,返回结果集
ORM 对象关系映射
- 数据库中的一张表对应java一个类,
- 数据表中的一条数据对应Java一个对象
查询单个对象get()---基础版本
@Testpublic 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加一个重载方法closeJDBCUtils.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_idprivate String orderName;//对应数据库表的order_nameprivate Date orderDate;//对应数据库表的order_date}@Testpublic 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 query(Class 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();//拿到描述当前结果集的元数据metadataResultSetMetaData 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对象,而不仅仅是一个
@Testpublic void testQueryList() throws Exception {
// Date date = new Date(System.currentTimeMillis());
// System.out.println(date); 2022-05-11String sql = "select id, name, email, birth from customers where id <= ?";List customers = queryList(Customer.class, sql, 20);for (Customer customer : customers) {System.out.println(customer);}}public List queryList(Class clazz, String sql, Object... args) throws Exception {List list = new ArrayList<>();//1. 获取连接Connection conn = JDBCUtils.getConnection();//2. 获取 PreparedStatement 用于发送 SQLPreparedStatement ps = conn.prepareStatement(sql);//3. 填充占位符for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}//4. 执行 SQL,获取 ResultSetResultSet rs = ps.executeQuery();//5. 获取当前结果集的元数据 ResultSetMetaDataResultSetMetaData 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();
并把图片保存到硬盘。
//查询图片@Testpublic 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版本号
- 5.1.7不好使
- 5.1.37好使
- 8.0好使
创建一个空表:emp。

假设我们要添加10万条数据到这个表,
1、下面这种就很慢,超级无敌变态慢
等了2分钟,才进去12000多条数据,等不下去了
@Testpublic 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():清空缓存的数据
通常我们会遇到两种批量执行SQL语句的情况:
- 多条SQL语句的批量处理;
- 一个SQL语句的批量传参;
在这之前,需要在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万条数据,快吗?
//批量处理@Testpublic 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){//批量发送sqlps.executeBatch();//清空sqlps.clearBatch();}}JDBCUtils.close(conn, ps, null);}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
