JDBC是指数据库连接技术,用于java连接mySQL等数据库。本文详细先容了尚硅谷课程中JDBC的学习内容和补充知识。
概述
- java语言只提供规范接口,存在于java.sql.javax.sql包下,然后数据库软件根据java提供的规范实现详细的驱动代码(jar)
- jar包是java步伐打成的一种压缩包格式,只要导入就可以利用对应方法
学习思路:(可以学完再看)
- 六大根本步骤获取连接,包罗直接输入字符串的Statement和改进版的PreparedStatement(通过占位符解决了容易SQL攻击的问题)
- JDBC的增删改查,此中插入数据需要思量主键自增长、批量插入效率低的问题
- 建立数据库事务(根本特征是关闭了自动提交,要同时完成多个操纵后再一起提交,如转账过程先增长A账户的钱,再扣除B账户的钱,一定要一起提交,防止出现只乐成一件事)
- 连接池,优化建立连接的过程(每件事都去连接很繁琐,浪费资源,Druid连接池)
- 硬编码:通过设置参数的方式
- 软编码:通过读取外部配置文件的方式(读取方法利用了类加载器,补充阐明在末端)
- 封装连接工具类(通过静态代码块的方式保证连接池只被创建一次,而不是每次调用方法都创建一个新的连接池对象。)
- V1.0:不同方法调用拿到的是不同连接对象
- V2.0:利用线程当地量的方法,保证同一个线程不同方法拿到的是同一个连接
- 完备工具类封装(连接池优化了建立连接,那么把增删改查操纵也封装一下吧)
- executeUpdate(实现数据的更新操纵,包罗增,删,改)
- executeQuery(实现数据的查询操纵,通过反射机制,输入模板对象,返回查询出的对象列表)
根本步骤
- 注册驱动,将依赖的jar包举行安装
- 建立连接 connection
- 创建发送SQL语句的对象statement
- statement对象去发送SQL数据到数据库获取返回结果
- 剖析结果集
- 销毁资源
mySQL端 创建t_user表- create database atguigu;
- use atguigu;
- create table t_user(
- id int primary key auto_increment comment '用户主键',
- account varchar(20) not null unique comment '账号',
- password varchar(64) not null comment '密码',
- nickname varchar(20) not null comment '昵称');
- insert into t_user(account,password,nickname) values('root','123456','经理'),('admin','666666','管理员');
复制代码 java端获取数据- public class StatementQuery {
- public static void main(String[] args) throws SQLException {
- //1.注册驱动
- DriverManager.registerDriver(new Driver());//8+驱动要选择带cj的
- //2. 获取连接,需要数据库ip地址(127.0.0.1),数据库端口号(3306),账号(root),密码,连接数据库的名称(atguigu)
- //jdbc:数据库厂商名//ip:端口号/数据库名
- Connection connection=DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建statement
- Statement statement = connection.createStatement();
- //4. 发送SQL语句
- String sql="select * from t_user";
- ResultSet resultSet = statement.executeQuery(sql);
- //5. 进行结果集更新
- while(resultSet.next()){
- int id = resultSet.getInt("id");
- String account = resultSet.getString("account");
- String password = resultSet.getString("password");
- String nickname = resultSet.getString("nickname");
- System.out.println(id+"--"+account+"--"+password+"--"+nickname);
- }
- //6. 关闭资源
- resultSet.close();
- statement.close();
- connection.close();
- }
- }
复制代码 模仿用户登录
- 模仿用户登录
- 键盘输入事件收集账号密码信息
- 输入账号密码举行查询验证是否登录乐成
- public class SatetementUserLoginPart {
- public static void main(String[] args) throws SQLException, ClassNotFoundException {
- //0. 获取用户输入信息
- Scanner scanner = new Scanner(System.in);
- System.out.println("请输入账号");
- String account = scanner.nextLine();
- System.out.println("请输入密码");
- String password = scanner.nextLine();
- //1.注册驱动,有两种方案
- /*方案一--------------
- //DriverManager.registerDriver(new Driver());//会注册两次驱动,可以考虑仅触发静态代码块
- //类加载机制:类记载时刻会触发静态代码块,可以通过new,静态方法,静态属性等触发
- */
- //方案二-------------
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //优点是字符串可以提取到外部的配置文件,便于更换数据驱动,会更加灵活
- //2. 获取数据库连接,共有三种方法
- //该方法是一个重载方法,允许开发者用不同的形式传入数据库连接的核心参数
- //三个参数-------------
- Connection connection=DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //jdbc:mysql:///atguigu 如果本机和3306可以省略为///
- //两个参数--------------
- /*
- Properties info=new Properties();
- info.put("user","root");
- info.put("password","root");
- DriverManager.getConnection("jdbc:mysql:///atguigu",info);
- //一个参数--------------
- //jdbc:数据库厂商名//ip:端口号/数据库名?user=root&password=root
- DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
- //其他可选信息,如果是sql8.0以后的版本可以省略serverTimezone=Asia/Shanghai&UseUnicode=true&characterEncoding=utf8&useSSL=true
- */
- //3. 创建发送SQL语句的Statetment对象
- Statement statement = connection.createStatement();
- //4. 发送SQL语句
- String sql="SELECT * FROM t_user WHERE account='"+account+"'AND PASSWORD='"+password+"';";
- //executeUpdate用于更新数据,返回影响的函数,executeQuery负责返回查询结果,返回结果封装对象
- ResultSet resultSet = statement.executeQuery(sql);
- //5.查询结果集解析,resultSet给出的是逐行的对象,使用next可以逐行提取下一个,遍历完毕时返回false
- /*仅查询的方案
- while(resultSet.next()){
- //光标已经移动,此时利用getInt\getString读取该行的数据
- //getString输入可以是index(列的下角标,从左向右从1开始),可以是列名(有别名写别名)
- int id = resultSet.getInt("id");
- String account1 = resultSet.getString("account");
- String password1 = resultSet.getString("password");
- String nickname = resultSet.getString("nickname");
- System.out.println(id+"--"+account+"--"+password+"--"+nickname);
- }
- */
- //考虑实际需求,只需要有一行数据证明就可以登录成功
- if(resultSet.next()){
- System.out.println("登录成功");
- }else {
- System.out.println("登录失败,用户名或密码错误");
- }
- //6. 关闭资源
- resultSet.close();
- statement.close();
- connection.close();
- }
- }
复制代码 存在几个问题
- SQL语句需要字符串拼接比较贫苦
- 只能拼接字符串类型,其他的数据库类型无法处理
- 可能发生注入攻击(动态值充当了SQL语句结构)
模仿用户登录(prepare改进)
- public class SPUserLoginPart {
- public static void main(String[] args) throws ClassNotFoundException, SQLException {
- //0. 获取用户输入信息
- Scanner scanner = new Scanner(System.in);
- System.out.println("请输入账号");
- String account = scanner.nextLine();
- System.out.println("请输入密码");
- String password = scanner.nextLine();
- //1.注册驱动,有两种方案
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //2. 获取数据库连接
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建发送SQL语句的preparedstatetment对象
- //(1). 编写SQL语句结构,动态值部分使用占位符?替代(2).创建preparedstatetment,传入动态值
- //(3). 动态值 占位符 赋值? 单独赋值即可 (4). 发送
- String sql="SELECT * FROM t_user where account=? and password=?;";
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- /*
- 参数一:index占位符的位置,从1开始
- 参数二:Object占位符的值,可以是任何类型
- */
- preparedStatement.setObject(1,account);
- preparedStatement.setObject(2,password);
- ResultSet resultSet = preparedStatement.executeQuery();
- //5. 结果集解析
- if(resultSet.next()){
- System.out.println("登录成功");
- }else {
- System.out.println("登录失败,用户名或密码错误");
- }
- //6. 关闭资源
- resultSet.close();
- preparedStatement.close();
- connection.close();
- }
- }
复制代码 JDBC实现增删改查
- public class PSCURDPart {
- @Test
- public void testInsert() throws ClassNotFoundException, SQLException {
- //1.注册驱动,有两种方案
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //2. 获取数据库连接
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建发送SQL语句的preparedstatetment对象
- String sql="insert into t_user(account,password,nickname) values(?,?,?)";
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- preparedStatement.setObject(1,"test");
- preparedStatement.setObject(2,"test");
- preparedStatement.setObject(3,"二狗");
- int rows = preparedStatement.executeUpdate();
- //5. 结果集解析
- if(rows>0){
- System.out.println("插入成功");
- }else {
- System.out.println("插入失败");
- }
- //6. 关闭资源
- preparedStatement.close();
- connection.close();
- }
- @Test
- public void testUpdate() throws ClassNotFoundException, SQLException {
- //1.注册驱动,有两种方案
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //2. 获取数据库连接
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建发送SQL语句的preparedstatetment对象
- String sql="update t_user set nickname=? where id=?";
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- preparedStatement.setObject(1,"张三");
- preparedStatement.setObject(2,3);
- int rows = preparedStatement.executeUpdate();
- //5. 结果集解析
- if(rows>0){
- System.out.println("修改成功");
- }else {
- System.out.println("修改失败");
- }
- //6. 关闭资源
- preparedStatement.close();
- connection.close();
- }
- @Test
- public void testDelete() throws ClassNotFoundException, SQLException {
- //1.注册驱动,有两种方案
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //2. 获取数据库连接
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建发送SQL语句的preparedstatetment对象
- String sql="delete from t_user where id=?";
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- preparedStatement.setObject(1,3);
- int rows = preparedStatement.executeUpdate();
- //5. 结果集解析
- if(rows>0){
- System.out.println("删除成功");
- }else {
- System.out.println("删除失败");
- }
- //6. 关闭资源
- preparedStatement.close();
- connection.close();
- }
- //查询所有用户数据,并封装到一个List<Map> 集合中,key=列名,value=列的内容
- @Test
- public void testSelect() throws ClassNotFoundException, SQLException {
- //1.注册驱动,有两种方案
- Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
- //2. 获取数据库连接
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- //3. 创建发送SQL语句的preparedstatetment对象
- String sql="SELECT id,account,password,nickname FROM t_user;";
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- ResultSet resultSet = preparedStatement.executeQuery();
- //5. 结果集解析
- List<Map> list=new ArrayList<>();
- ResultSetMetaData metaData = resultSet.getMetaData();//获取当前结果集列的信息对象
- int columnCount = metaData.getColumnCount();//为了水平遍历
- while(resultSet.next()){
- Map map=new HashMap();
- for (int i = 1; i <columnCount ; i++) {
- Object value = resultSet.getObject(i);
- String columnLabel = metaData.getColumnLabel(i);//getlabel可以得到别名,name只能别名
- map.put(columnLabel,value);
- }
- list.add(map);
- }
- System.out.println(list);
- //6. 关闭资源
- preparedStatement.close();
- connection.close();
- }
- }
复制代码 批量插入
- public class PSOthePart {
- //t_user 插入一条数据,并获取数据库自增长的主键
- @Test
- public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
- Class.forName("com.mysql.cj.jdbc.Driver");
- Connection connection= DriverManager.
- getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
- String sql="Insert into t_user(account,password,nickname) values(?,?,?);";
- //插入数据同时获取返回主键
- PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
- preparedStatement.setObject(1,"test1");
- preparedStatement.setObject(2,"123456");
- preparedStatement.setObject(3,"蛋");
- int i=preparedStatement.executeUpdate();
- if(i>0){
- System.out.println("插入成功");
- //获取主键的结果集对象,一行一列,id=值
- ResultSet resultSet = preparedStatement.getGeneratedKeys();
- resultSet.next();
- int id=resultSet.getInt(1);
- System.out.println("id="+id);
- }else{
- System.out.println("插入失败");
- }
- preparedStatement.close();
- connection.close();
- }
- }
复制代码 数据库事务
- 答应在失败环境下,数据回归到业务之前的状态!
- 具有原子性(不可切分)、一致性(状态稳定)、隔离性(互不干扰)、长期性(永久性改变)
- 手动提交
实现目标:实现两个账户之间的安全转账,如果一方余额不足不会出现转账错误(即一方账户增长了另一方没有淘汰)
业务表的创建
- public void returnPrimaryKey2() throws ClassNotFoundException, SQLException {
- Class.forName("com.mysql.cj.jdbc.Driver");
- // 1.路径后面添加允许批量操作
- Connection connection= DriverManager.
- getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true","root","mypassword");
- String sql="Insert into t_user(account,password,nickname) values(?,?,?);";
- PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
- long start=System.currentTimeMillis();
- for (int i = 1; i < 10000; i++) {
- preparedStatement.setObject(1,"dd"+i);
- preparedStatement.setObject(2,"dd"+i);
- preparedStatement.setObject(3,"蛋"+i);
- preparedStatement.addBatch();//2. 不执行,追加到values后面,批量添加addBatch
- }
- preparedStatement.executeBatch();//3. 执行批量操作
- long end = System.currentTimeMillis();
- preparedStatement.close();
- connection.close();
- }
复制代码 BankService.java(实现业务操纵,即转账)
- CREATE TABLE t_bank(
- id INT PRIMARY KEY AUTO_INCREMENT,
- account VARCHAR(20) NOT NULL UNIQUE COMMENT '账户',
- money INT UNSIGNED COMMENT '金额,不能为负值'
- );
- INSERT INTO t_bank(account,money) VALUES
- ('ergouz1',1000),('lvdandan',1000);
复制代码 BankDao.java(业务实现细节,即账户钱增长和淘汰)
- public class BankService {
- @Test
- public void start() throws SQLException, ClassNotFoundException {
- transfer("lvdandan","ergouz1",500);
- }
- public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
- Class.forName("com.mysql.cj.jdbc.Driver");
- // 1.路径后面添加允许批量操作
- Connection connection= DriverManager.
- getConnection("jdbc:mysql:///atguigu","root","mypassword");
- BankDao bankDao=new BankDao();
- try{
- connection.setAutoCommit(false);//关闭自动提交,开启手动提交
- bankDao.add(addAccount,money,connection);
- System.out.println("----");
- bankDao.sub(subAccount,money,connection);
- connection.commit();//事务提交
- }catch (Exception e){
- connection.rollback();
- throw e;
- }finally {
- connection.close();
- }
- }
- }
复制代码 连接池
- 每次获取新的连接消耗很大,为了节约创建和销毁连接的性能消耗
- javax.sql.DataSource接口,规范了连接池获取连接的方法,规范了连接池回收连接的方法,连接池有多种,但都实现了该接口
- 硬编码:通过设置参数的方式
- 软编码:通过读取外部配置文件的方式(读取方法利用了类加载器,补充阐明在末端)
- public class BankDao {
- public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
- String sql="Update t_bank set money=money+? where account=?;";
- PreparedStatement statement = connection.prepareStatement(sql);
- statement.setObject(1,money);
- statement.setObject(2,account);
- statement.executeUpdate();
- statement.close();
- }
- public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
- String sql="Update t_bank set money=money-? where account=?;";
- PreparedStatement statement = connection.prepareStatement(sql);
- statement.setObject(1,money);
- statement.setObject(2,account);
- statement.executeUpdate();
- statement.close();
- }
- }
复制代码 druid.properties- public class DruidUsePart {
- //直接使用代码设置连接池连接参数等方式
- @Test
- public void testHard() throws SQLException {
- DruidDataSource dataSource=new DruidDataSource();
- //连接数据库驱动类的全限定符 注册驱动
- dataSource.setUrl("jdbc:mysql:///atguigu");
- dataSource.setUsername("root");
- dataSource.setPassword("mypassword");
- dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
- dataSource.setInitialSize(5);//初始化连接数量
- dataSource.setMaxActive(10);//最大的数量
- DruidPooledConnection connection = dataSource.getConnection();//获取连接
- //数据库操作
- //
- connection.close();
- }
- //读取外部配置文件的方法实例化连接对象
- public void testSoft() throws Exception {
- Properties properties = new Properties();
- InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
- properties.load(ips);
- DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
- Connection connection = dataSource.getConnection();
- //数据库操作
- //
- connection.close();
- }
- }
复制代码 连接工具类封装
通过静态代码块的方式保证连接池只被创建一次,而不是每次调用方法都创建一个新的连接池对象
V1.0
- #key = value => java properties读取
- #druid的key必须固定命名
- driverClassName=com.mysql.cj.jdbc.Driver
- username=root
- password=123456
- url=jdbc:mysql://127.0.0.1:3306/atguigu
复制代码 问题:不同方法调用拿到的是不同对象,思量如何同一个线程不同方法拿到的是同一个连接
V2.0(线程当地量)
利用线程当地变量,存储连接信息 确保一个线程的多个方法可以获取同一个connection
- public class JdbcUtils {
- private static DataSource dataSource = null;//连接池对象
- static{
- //初始化连接池对象
- Properties properties = new Properties();
- InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
- try {
- properties.load(is);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- try {
- dataSource = DruidDataSourceFactory.createDataSource(properties);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- public static Connection getConnection() throws SQLException {
- return dataSource.getConnection();
- }
- public static void freeConnection(Connection connection) throws SQLException {
- connection.close();//连接池的连接执行回收
- }
- }
复制代码- 优势:事务操作的时候server 和 dao 属于同一个线程,不用再传递参数
复制代码
- 大家都可以调用getConnection自动获取的是相同的连接池
复制代码 V2版本代码- import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.io.IOException;import java.io.InputStream;import java.sql.Connection;import java.sql.SQLException;import java.util.Properties;public class JdbcUtilsV2 { private static DataSource dataSource = null;//连接池对象 大家都可以调用getConnection自动获取的是相同的连接池 static{ //初始化连接池对象 Properties properties = new Properties(); InputStream is = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties"); try { properties.load(is); } catch (IOException e) { throw new RuntimeException(e); } try { dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { throw new RuntimeException(e); } } public static Connection getConnection() throws SQLException { //先查看线程当地变量中是否存在 Connection connection = threadLocal.get(); if(connection == null){ //线程当地变量没有,连接池获取 connection = dataSource.getConnection(); threadLocal.set(connection); } return dataSource.getConnection(); } public static void freeConnection() throws SQLException { Connection connection = threadLocal.get(); if(connection != null){ threadLocal.remove();//清空当地变量数据 connection.setAutoCommit(true); connection.close();//连接池的连接执行回收 } }}
复制代码 此时的封装只针对了获取连接部分,还不够美满,可以创建一个更加美满的工具类,包罗数据库的增删改查
完备工具类(修改和查询)
类代码
[code]package utils;import java.lang.reflect.Field;import java.sql.*;import java.util.ArrayList;import java.util.List;/** * Description:封装dao层数据库重复代码 * TODO:封装两个方法 * 一个简化非DQL * 一个简化DQL(查询语句) */public class BaseDao { /** * 封装简化非DQL语句 * @param sql 带占位符的SQL语句 * @param params 占位符的值 留意:传入的占位符的值,必须即是SQL语句?位置的值 * @return 执行影响的行数 */ public int executeUpdate(String sql, Object... params) throws SQLException {//Object...可变参数传参数形式,在代码中可以作为数组利用 //获取连接 Connection connection = JdbcUtilsV2.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); //可变参数可当作数组利用 for (int i = 1; i 没有开启事物,正常回收连接 JdbcUtilsV2.freeConnection(); } return rows; } /** * 将查结果封装到一个实体类集合 * @param clazz 要接值的实体类集合的模板对象 * @param sql 查询语句,要求类名或者别名即是实体类的属性名 u_id as uId => uId * @param params 占位符的值 要和 ? 位置对象对应 * @return 查询的实体类集合 * @param 声明的结果的泛型 * @throws SQLException * @throws InstantiationException * @throws IllegalAccessException * @throws NoSuchFieldException */ //第一个是方法泛型,需要调用时确定 // 第三个T是指利用反射技术赋值 public List executeQuery(Class clazz,String sql,Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException { Connection connection = JdbcUtilsV2.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); if(params != null && params.length != 0) { for (int i = 1; i |