Java学习篇基础加强篇30

it2023-10-17  73

上一篇博客:Java学习篇基础加强篇29_MySQL篇【基础、约束、备份、多表查询、事务】


写在前面:

Java学习篇系列博客到这里已经基本完成,从Java学习篇0_JDK-11.0.7安装和Java简单理解

至此,JavaSE部分基本已经学习完成,本篇JDBC及下篇JDBC连接池及上篇MySQL篇属于基础加强篇,为后面Java Web、JavaEE的学习做准备

坚持此系列博客的记录也使我收获了很多,记录博客不仅是对学习的一种记录,而且便于回顾已经学习的知识~

虽然脑子不太好使,但是我有一颗炽热的心~坚持学习也能不断进步 (*^▽^*)

最后,还是那句话

虽然我走的很慢,但我仍在前进!


目录:

JDBC基本概念快速入门之对JDBC中各个接口和类详解事务操作数据库连接池Spring JDBC :JDBC Template

开始

一、JDBC基本概念

概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库 JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

简单理解就是接口,我们不需要自己设计实现这个接口,只需要知道怎么使用即可

之前已经学习了操作数据库的方式

使用第三方客户端来访问 MySQL:SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL使用MySQL自带的命令行方式

JDBC就是通过Java来访问MySQL数据库

1.1使用JDBC的好处

开发访问数据库的程序,只需要调用JDBC接口中的方法即可同一套Java代码,少量的修改可访问JDBC支持的其他数据库…

1.2使用JDBC开发使用的包

java.sql:所有与 JDBC 访问数据库相关的接口和类javax.sql:数据库扩展包,提供数据库额外的功能。如:连接池数据库驱动:各大数据库厂商提供,需要额外下载(一般在安装的数据库文件夹可以找到)是对JDBC接口的实现的类

1.3JDBC的核心API

DriverManager类:管理和注册数据库驱动,得到数据库连接对象Connection接口:一个连接对象,可创建Sattement和PreparedStatement对象Sattement接口:一个SQL语句对象,用于将SQL语句发送给数据库服务器PreparedStatement接口:一个Sattement接口的子接口,功能更强大(防止SQL注入)ResultSet接口:用于封装数据库查询的结果集,返回给客户端Java程序

二、JDBC快速入门

2.1导入驱动jar包

导入驱动jar包注册驱动:把JDBC实现类加载到内存,Driver里面有静态代码块,创建 Driver对象获取连接对象

2.2DriverManager类

作用:

管理和注册驱动创建数据库的连接

类中的方法:

static Connection getConnection(String url, Properties info) 试图建立到给定数据库 URL 的连接。 (配置文件的方式Properties映射)static Connection getConnection(String url, String user, String password) 试图建立到给定数据库 URL 的连接。 若出现乱码,还可以指定字符集

2.3Connection接口

作用:

Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。

方法:

Statement createStatement()创建一条 SQL 语句对象

2.4Statement接口

访问数据库的步骤:

注册和加载驱动(可以省略)获取连接Connection获取Statement对象返回结果集释放资源

作用:

代表一条语句对象,用于发送SQL语句给服务器,用于执行静态SQL语句 并返回他所生成的对象(就是找个对象传入SQL语句执行,返回一个SQL语句对象)

方法:

-int executeUpdate(String sql) 用于发送 DML 语句,增删改的操作,insert、update、delete 参数:SQL 语句 返回值:返回对数据库影响的行数

ResultSet executeQuery(String sql)用于发送 DQL 语句,执行查询的操作。select 参数:SQL 语句 返回值:查询的结果集

释放资源:

需要释放的对象::ResultSet 结果集,Statement 语句,Connection 连接

释放元则:先开的后关,后开的先关,ResultSet 》Statement 》 Connection

放在finally代码块中

2.5小练习

执行DDL操作

需求:创建一张学生表

接着DML操作

需求:向学生表中添加4条记录,主键是自动增长,然后查询打印表中记录**

import com.mysql.jdbc.Connection; import javax.xml.transform.Result; import java.lang.invoke.StringConcatFactory; import java.sql.*; import java.util.Properties; public class Demo1_JiaZaiQuDong { public static void main(String[] args) throws ClassNotFoundException, SQLException { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获得连接对象 String url = "jdbc:mysql://localhost:3306/jdbc"; //Connection conn = (Connection) DriverManager.getConnection(url, "root", "720720"); //采用配置文件的方式 Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","720720"); Connection conn2 = (Connection) DriverManager.getConnection(url,info); // System.out.println(conn2); //通过连接对象获得语句对象 Statement stmt = conn2.createStatement(); stmt.executeUpdate("create table if not exists student (id int PRIMARY key auto_increment, " + "name varchar(20) not null, gender boolean, birthday date)"); //插入三条数据 int count = 0;//计数影响的行数 count += stmt.executeUpdate("insert into student values(null, 'xiaosi', 1, '1993-03-24')"); count += stmt.executeUpdate("insert into student values(null, 'xiaochao', 0, '1994-03-24')"); count += stmt.executeUpdate("insert into student values(null, 'xiaolong', 1, '1995-03-24')"); System.out.println("插入了 :" + count + "条数据"); //查询插入的记录 ResultSet rs = stmt.executeQuery("select * from student"); //循环取出数据 while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); boolean gender = rs.getBoolean("gender"); Date birthday = rs.getDate("birthday"); System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday); } rs.close(); stmt.close(); conn2.close(); } }

使用DQL查询记录

关于ResultSet接口中注意事项:

开始是游码指向表头记录,此时使用rs.getXxx会报错:Before start of result set如果游码在最后一行,此时使用rs.getXxx会报错: After end of result set使用完毕以后要关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection

2.6数据库工具类JDBCUtils

什么时候自己创建工具类

一个功能经常使用,我们把这个功能实现单独封装到一个工具类,可以在不同的地方重用就是上面写的代码包含很多重复的代码,把重复的代码抽取出来

创建类JdbcUtiles包含3个方法:

可以把几个字符串定义类内成员常量:用户名、密码、URL、驱动类(放在静态代码块中)得到数据库的连接:getConnection()关闭所有资源:close(Connection conn, Statement stmt),close(Connection conn, Statement stmt, ResultSet rs)

自定义的工具类

import java.sql.*; public class Demo2_JdbcUtils { //定义成员常量 private static final String USER = "root"; private static final String PWD = "720720"; private static final String URL = "jdbc:mysql://localhost:3306/jdbc"; private static final String DRIVER = "com.mysql.jdbc.Driver"; //加载驱动 static{ try{ Class.forName(DRIVER); }catch (ClassNotFoundException e){ e.printStackTrace(); } } //得到连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL,USER,PWD); } //关闭资源 //使用重载的方式 public static void close(Connection conn, Statement stmt){ if(stmt != null){ try{ try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } } public static void close(Connection conn, Statement stmt, ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } close(conn,stmt); } } }

我们在主方法中写的代码是不是就少了,以后修改工具类就能完成不同的操作

import java.sql.*; public class Demo2_TestMain { public static void main(String[] args) throws SQLException { Connection conn = Demo2_JdbcUtils.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from student"); //循环取出数据 while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); boolean gender = rs.getBoolean("gender"); Date birthday = rs.getDate("birthday"); System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday); } //关闭资源 Demo2_JdbcUtils.close(conn,stmt,rs); } }

2.7登录小案例

需求:

有一张用户表添加几条记录登录就是查询记录,用户名、密码正确才能查出来,就是登录成功

步骤:

得到用户从控制台输入的用户名和密码来查询数据库写一个登录的方法 通过工具类得到连接创建语句对象,使用拼接字符串的方式生成SQL语句(这里先提示,存在SQL注入的漏洞,子类PreparedStatement能防止SQL注入t)查询数据库,有记录则登录成功释放资源 import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class Demo3_LoginTest { public static void main(String[] args) throws SQLException { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } //登录的方法 private static void login(String name, String password) throws SQLException { //提升作用域,待会方便释放资源 Connection conn = null; Statement stmt = null; ResultSet rs = null; try { conn = Demo2_JdbcUtils.getConnection(); stmt = conn.createStatement(); String sql = "select * from user where name='" + name + "' and password='" + password + "'"; rs = stmt.executeQuery(sql); System.out.println(sql); if(rs.next()){ System.out.println("登录成功"); } else{ System.out.println("登录失败!"); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { //释放资源 Demo2_JdbcUtils.close(conn,stmt,rs); } } }

存在SQL注入的问题

关于SQL注入可以了解往期博客:网络安全学习篇45_第四阶段_SQL注入、SQLmap

我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,

改变了原有 SQL 真正的意义,以上问题称为 SQL 注入。

要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。

2.8 PreparedStatement 接口

Statement的子接口,功能更加强大

PreparedSatement 的好处

prepareStatement()会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用着预编译后的结果。 可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。安全性更高,没有 SQL 注入的隐患。提高了程序的可读性

创建方法:

Connection 创建 PreparedStatement 对象

PreparedStatement prepareStatement(String sql) 指定预编译的 SQL 语句,SQL 语句中使用占位符? 创建一个语句对象

PreparedStatement 接口中的方法:

int executeUpdate()执行 DML,增删改的操作,返回影响的行数。ResultSet executeQuery() 执行 DQL,查询的操作,返回结果集

使用步骤:

编写 SQL 语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?";获得 PreparedStatement 对象设置实际参数:setXxx(占位符的位置, 真实的值)执行参数化 SQL 语句关闭资源

给占位符设置值传参:

void setDouble(int parameterIndex, double x)将指定参数设置为给定 Java double 值。void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。 -void setInt(int parameterIndex, int x)将指定参数设置为给定 Java int 值。void setLong(int parameterIndex, long x)将指定参数设置为给定 Java long 值。void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。void setString(int parameterIndex, String x)将指定参数设置为给定 Java String 值。

2.9登录案例小改进,使用PreparedStatment接口

import java.sql.*; import java.util.Scanner; public class Demo3_LoginTest { public static void main(String[] args) throws SQLException { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } //登录的方法 private static void login(String name, String password) throws SQLException { //提升作用域,待会方便释放资源 Connection conn = null; //Statement stmt = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = Demo2_JdbcUtils.getConnection(); String sql = "select * from user where name=? and password=?"; ps = conn.prepareStatement(sql); ps.setString(1, name); ps.setString(2,password); //设置参数 //改进,使用PreparedStatement接口,防止SQL注入 rs = ps.executeQuery(); //rs = stmt.executeQuery(sql); System.out.println(sql); if(rs.next()){ System.out.println("登录成功"); } else{ System.out.println("登录失败!"); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { //释放资源 Demo2_JdbcUtils.close(conn,ps,rs); } } }

2.10小案例-数据对象

表和类的关系

需求:

使用PreparedStatement查询多条数据,封装成一个学生对象集合List<student>,集合中每个元素是一个 JavaBean 实体类

student类

import java.util.Date; public class Student { private int id; private String name; private boolean gender; private Date birthday; public Student() { } public Student(int id, String name, boolean gender, Date birthday) { this.id = id; this.name = name; this.gender = gender; this.birthday = birthday; } public int getId() { return id; } public String getName() { return name; } public boolean isGender() { return gender; } public Date getBirthday() { return birthday; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGender(boolean gender) { this.gender = gender; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", gender=" + gender + ", birthday=" + birthday + '}'; } } 主方法类 import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Demo4_StudentList { public static void main(String[] args) { List<Student> stus = new ArrayList<>(); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = Demo2_JdbcUtils.getConnection(); ps = conn.prepareStatement("select * from student"); rs = ps.executeQuery(); while(rs.next()){ Student student = new Student(); student.setId(rs.getInt("id")); student.setName(rs.getString("name")); student.setGender(rs.getBoolean("gender")); student.setBirthday(rs.getDate("birthday")); stus.add(student); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { Demo2_JdbcUtils.close(conn,ps,rs); } for (Student student : stus) { System.out.println(student); } } }

三、事务管理

之前我们是使用 MySQL 的命令来操作事务。接下来我们使用 JDBC 来操作银行转账的事务

准备数据

API介绍:

Connection 接口中与事务有关的方法 说明

void setAutoCommit(boolean autoCommit) 参数是 true 或 false 如果设置为 false,表示关闭自动提交,相当于开启事务void commit()提交事务void rollback()回滚事务

开发步骤:

获取连接开启事务获取到 PreparedStatement使用 PreparedStatement 执行两次更新操作正常情况下提交事务出现异常回滚事务最后关闭资源

案例代码

import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Demo5_ShiWuGuanLi { public static void main(String[] args) { //1) 注册驱动 Connection connection = null; PreparedStatement ps = null; try { //2) 获取连接 connection = Demo2_JdbcUtils.getConnection(); //3) 开启事务 connection.setAutoCommit(false); //4) 获取到 PreparedStatement //从 jack 扣钱 ps = connection.prepareStatement("update account set balance = balance - ? where name=?"); ps.setInt(1, 500); ps.setString(2,"Jack"); ps.executeUpdate(); //出现异常 //System.out.println(100 / 0); //给 rose 加钱 ps = connection.prepareStatement("update account set balance = balance + ? where name=?"); ps.setInt(1, 500); ps.setString(2,"Rose"); ps.executeUpdate(); //提交事务 connection.commit(); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); try { //事务的回滚 connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } System.out.println("转账失败"); } finally { //7) 关闭资源 Demo2_JdbcUtils.close(connection,ps); } } }

创造异常System.out.println(100 / 0);

四、数据库连接池

概念:

其实就是一个容器(集合),存放数据库连接对象的容器当系统初始化后,容器被创建,容器中会申请一些对象,当用户访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器

好处:

节约资源用户访问高效

实现:

标准接口:javax.sql包下的DataSource 接口 有数据库厂商实现,我们只需要lib然后使用

方法:

获取连接:getConnection()归还连接:Connection.close() 如果连接对象是从连接池中获取的,那么调用此时close()不是关闭连接,而是归还连接

一般我们不去实现它,有数据库厂商实现,我们只要会使用:

C3P0:数据库连接池技术Druid:数据库连接技术,由阿里巴巴提供,效率很高…

4.1C3P0数据库连接池技术

步骤:

导入jar包:(两个) c3p0-0.9.5.2.jar 和 mchange-commons-java-0.2.12.jar , 不要忘记导入数据库驱动jar包

定义配置文件

名称:c3p0.properties 或者 c3p0-config.xml路径:放在src目录下即可

创建核心对象:数据库连接池对象 ComboPooledDataSource

获取连接

代码:

//1.创建数据库连接池对象 DataSource ds = new ComboPooledDataSource(); //2. 获取连接对象 Connection conn = ds.getConnection();

4.2Druid数据库连接池技术

步骤:

导包:导入jar包 druid-1.0.9.jar定义配置文件: 是properties形式的可以任意名称,可以放在任意目录下 加载配置文件.properties获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory获取连接:getZConnection

代码:

//3.加载配置文件 Properties pro = new Properties(); //配置文件以流的形式进内存 InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //4.获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //5.获取连接 Connection conn = ds.getConnection();

6.3定义工具类使用Druid

步骤:

定义一个类JdbcUtils提供静态底阿妈快加载配置文件,初始化连接池对象提供方法 获取连接的方法:通过数据库连接池获取连接释放资源获取连接池的方法

代码:

配置文件druid.properties driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///jdbc username=root password=720720 # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000 工具类 import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class Demo6_DruidJdbcUtils { //定义成员变量 private static DataSource ds; static { //加载配置文件 Properties pro = new Properties(); try { pro.load(Demo6_DruidJdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties")); //获取DataSource ds = DruidDataSourceFactory.createDataSource(pro); } catch (Exception e) { e.printStackTrace(); } } //获取连接 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //释放资源重载的形式 public static void close(Statement stmt,Connection conn){ close(null,stmt,conn); } public static void close(ResultSet rs, Statement stmt, Connection conn){ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } //获取连接池的方法 public static DataSource getDataSource(){ return ds; } } 测试主类 import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Properties; public class Demo6_DruidTestMain { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; try{ conn = Demo6_DruidJdbcUtils.getConnection(); String sql = "insert into account values(null,?,?)"; ps = conn.prepareStatement(sql); ps.setString(1,"xiaosi"); ps.setDouble(2,10000); int count = ps.executeUpdate(); System.out.println("共 " + count + " 行受到影响~"); }catch(SQLException e){ e.printStackTrace(); } finally { Demo6_DruidJdbcUtils.close(ps,conn); } } }

五、Spring JDBC

Spring框架对JDBC的简单封装,提供一个JDBCTemplate对象简化JDBC的开发

步骤:

导入jar包

创建JdbcTemplete对象,依赖于数据源DataSource JdbcTemplate template = new JdbcTemplate(ds);

调用JdbcTemplete的方法完成CRUD的操作

update():执行DML语句。增、删、改语句queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合 注意:这个方法查询的结果集长度只能是1queryForList():查询结果将结果集封装为list集合 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中query():查询结果,将结果封装为JavaBean对象 4.1. query的参数:RowMapper 4.2. 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装 new BeanPropertyRowMapper<类型>(类型.class)queryForObject:查询结果,将结果封装为对象 一般用于聚合函数的查询

5.1案例小练习

需求:

修改1号数据的 balance 为 5000添加一条记录删除刚才添加的记录查询id为1的记录,将其封装为Map集合查询所有记录,将其封装为List查询所有记录,将其封装为Account对象的List集合查询总记录数

代码:

import org.junit.Test; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; import java.util.Map; public class Demo7_JdbcTemplateTest { //Junit单元测试,可以让方法独立运行 //0. 获取JDBcTemplate对象 private JdbcTemplate template = new JdbcTemplate(Demo6_DruidJdbcUtils.getDataSource()); //1. 修改1号数据salary为10000 @Test public void test1(){ //2. 定义sql String sql = "update account set balance = 5000 where id = 1"; //3. 执行sql int count = template.update(sql); System.out.println("更新了 " + count + " 条数据~"); } // 2. 添加一条记录 @Test public void test2(){ String sql = "insert into account(id,name,balance) values(null,?,?)"; int count = template.update(sql, "郭靖", 10); System.out.println("插入了 " + count + " 条数据~"); } //3. 删除一条记录 @Test public void test3(){ String sql = "delete from account where id = ?"; int count = template.update(sql, 1); System.out.println("删除了 " + count + " 条数据~"); } //4. 查询id = 3的记录,封装换成Map集合 @Test public void test4(){ String sql = "select * from account where id = ?"; Map<String,Object> map = template.queryForMap(sql,3); System.out.println(map); } //5. 查询所有记录,封装成List @Test public void test5(){ String sql = "select * from account"; List<Map<String, Object>> list = template.queryForList(sql); for (Map<String, Object> stringObjectMap : list) { System.out.println(stringObjectMap); } } //6. 查询所有记录,将其封装为Account对象的List集合 @Test public void test6_2(){ String sql = "select * from account"; List<Account> list = template.query(sql, new BeanPropertyRowMapper<Account>(Account.class)); for (Account acc : list) { System.out.println(acc); } } //7. 查询总记录数 @Test public void test7(){ String sql = "select count(id) from account"; Long total = template.queryForObject(sql, Long.class); System.out.println("总记录数为" + total); } }

更新记录

插入一条记录

删除一条记录

查询一条记录,封装成Map对象

所有记录,封装成list集合

查询所有记录,将其封装为Account对象的List集合 查询数据记录个数

参考:B站黑马

最新回复(0)