一、Javaweb之JDBC、Maven、MyBatis

1. JDBC

前置知识:mysql基础

  • JDBC: Java DataBase Connectivity

    使用Java语言操作关系型数据库(多种数据库)的一套API
    在这里插入图片描述

1.1 JDBC步骤

  • 创建工程,导入驱动jar包
    在这里插入图片描述

  • 注册驱动

    Class.forName("com.mysql.jdbc.Driver");
    
  • 获取连接

    Connection conn = DriverManager.getConnection(url, username, password);
    

    Java代码需要发送SQL给MySQL服务端,就需要先建立连接

  • 定义SQL语句

    String sql =update…” ;
    
  • 获取执行SQL对象

    执行SQL语句需要SQL执行对象,而这个执行对象就是Statement对象

    Statement stmt = conn.createStatement();
    
  • 执行SQL

    stmt.executeUpdate(sql);  
    
  • 处理返回结果

  • 释放资源

1.2 测试:连接数据库

  • (1)创建工程,导入驱动jar包
    创建一个空项目,并配置JDK
    在这里插入图片描述

    右键项目,新建一个java模块jdbc-demo
    在这里插入图片描述

    在jdbc-demo下新建一个Directory,命令为lib,并在其中导入jar包(可以在mvnrepository官网中下载)
    在这里插入图片描述

    右击jar包,选择 add as library,再选择模块有效

  • (2)JDBCDemo.class

    package org.example.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.Statement;
    
    public class JDBCDemo {
          
          
        public static void main(String[] args) throws Exception{
          
          
            //1. 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2. 获取连接
            String url = "jdbc:mysql://localhost:3306/test?useSSL=false";//这里的test是数据库名; useSSL是版本不兼容的问题,根据是否报错来决定是否添加
            String username = "root";
            String password = "123456";
            Connection conn = DriverManager.getConnection(url, username, password);
            //3. 定义SQL语句
            String sql = "update emp set salary = 30000 where id = 1";
            //4. 获取执行SQL对象
            Statement stmt = conn.createStatement();
            //5. 执行sql
            int count = stmt.executeUpdate(sql);//返回被影响的行数
            //6. 处理结果
            System.out.println(count);
            //7 释放资源
            stmt.close();
            conn.close();
        }
    }
    

1.3 JDBC API详解

DriverManager

  • 注册驱动

    Class.forName("com.mysql.jdbc.Driver");
    

    com.mysql.jdbc.Driver类源码
    在这里插入图片描述

    • 在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注册了,那么只需要加载 Driver 类,该静态代码块就会执行。而 Class.forName("com.mysql.jdbc.Driver"); 就可以加载 Driver
    • 实际上Class.forName(“com.mysql.jdbc.Driver”)这句不写也能执行成功,因为jar包会自动加载驱动类
      在这里插入图片描述
  • 获取连接

    Connection conn = DriverManager.getConnection(url, username, password);
    

    参数说明:

    • url : 连接路径

      语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
      示例:jdbc:mysql://localhost:3306/test?useSSL=false

Connection

  • 获取执行SQL的对象

    Statement stmt = conn.createStatement();
    
    • 普通执行SQL对象

      Statement createStatement()
      
    • 预编译SQL的执行SQL对象:防止SQL注入

      PreparedStatement  prepareStatement(sql)
      
    • 执行存储过程的对象(一般不会用到)

      CallableStatement prepareCall(sql) #通过这种方式获取的CallableStatement执行对象是用来执行存储过程的
      
  • 事务管理

    Connection接口中定义了3个对应的方法:

    void setAutoCommit(boolean autoCommit);//开启事务:true表示自动提交事务,false表示手动提交事务
    void commit();//提交事务
    void rollback();//回滚事务
    

    示例

    public class JDBCDemo {
          
          
        public static void main(String[] args) throws Exception{
          
          
            //1. 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2. 获取连接
            String url = "jdbc:mysql://localhost:3306/test?useSSL=false";//这里的test是数据库名; useSSL是版本不兼容的问题,根据是否报错来决定是否添加
            String username = "root";
            String password = "123456";
            Connection conn = DriverManager.getConnection(url, username, password);
            //3. 定义SQL语句
            String sql1 = "update emp set salary = 35000 where id = 1";
            String sql2 = "update emp set salary = 20000 where id = 2";
            //4. 获取执行SQL对象
            Statement stmt = conn.createStatement();
            try{
          
          
                //开启事务
                conn.setAutoCommit(false);//设置为手动提交
                //5. 执行sql
                int count1 = stmt.executeUpdate(sql1);
                int i = 3/0;//模拟异常发生
                int count2 = stmt.executeUpdate(sql1);
                //6. 处理结果
                System.out.println(count1+", "+count2);
                //提交事务
                conn.commit();//确保sql1和sql2是一起成功的
            }catch (Exception e){
          
          
                //回滚事务
                conn.rollback();
                e.printStackTrace();
            }
            //7 释放资源
            stmt.close();
            conn.close();
        }
    }
    
    

    触发回滚,最终sql1和sql2都失败了

Statement

int count = stmt.executeUpdate(sql);

作用:执行SQL语句

  • executeUpdate:执行DDL、DML语句

    int executeUpdate(sql);//DDL可能返回0
    
  • executeQuery:执行DQL语句

    ResultSet executeQuery(sql);//返回值:ResultSet结果集对象
    

ResultSet

boolean next();//将光标从当前位置向前移动一行,判断当前行是否为有效行
xxx getXxx(参数);//获取数据
  • xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
  • 参数
    • int类型的参数:列的编号,从1开始
    • String类型的参数: 列的名称

示例:查询account表,并将结果存储在ArrayList之中

  • 创建Account实体类

    package org.example.pojo;
    //account表里面有3个属性,注意Account类里的属性名称要和数据库表名称相同
    public class Account {
          
          
        private int id;
        private String name;
        private double money;
        //后面的getter和setter方法,以及toString方法省略,可自动生成
    }
    
    
  • 查询数据库

    String sql = "select * from account";
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    List<Account> list = new ArrayList<>();
    while(rs.next()){
          
          
        Account account = new Account();
        //int id = rs.getInt(1);//按列编号获取
        int id = rs.getInt("id");//按列名获取
        //String name = rs.getNString(2);
        String name = rs.getNString("name");
        //double money = rs.getDouble(3);
        double money = rs.getDouble("money");
        account.setId(id);
        account.setName(name);
        account.setMoney(money);
        list.add(account);
    }
    System.out.println(list);
    

PreparedStatement

  • 作用:预编译SQL语句并执行:预防SQL注入问题
  • SQL注入:通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法
  • 正常查询

    String name = "zhangsan";
    String pwd = "123";
    
    String sql = "select * from user where name = '"+name+"' and pwd = '"+pwd+"'";
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    
    if(rs.next()) System.out.println("登录成功");
    else System.out.println("登录失败");
    
  • SQL注入

    String name = "fsfdfsg";//随意写
    String pwd = "' or '1' = '1";//sql注入
    
    String sql = "select * from user where name = '"+name+"' and pwd = '"+pwd+"'";
    System.out.println(sql);
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    
    if(rs.next()) System.out.println("登录成功");
    else System.out.println("登录失败");
    //在name和pwd不对的情况下,最终结果是登录成功
    

    这是因为sql语句的问题,打印上面的sql语句,可以看到

    select * from user where name = 'fsfdfsg' and pwd = '' or '1' = '1'
    

    显然,该语句会返回user表中所有行,从而导致错误登录,这就是SQL注入

  • 解决SQL注入:PreparedStatement

    • 获取PreparedStatement对象

      // SQL语句中的参数值,使用?占位符替代
      String sql = "select * from user where username = ? and password = ?";
      // 通过Connection对象获取,并传入对应的sql语句
      PreparedStatement pstmt = conn.prepareStatement(sql);
      
    • 设置参数值

      setXxx(参数1, 参数2);//给 ? 赋值,参数同样可以是编号(从1开始),也可以是名称
      
    • 执行SQL:不需要传递sql

      executeUpdate(); //执行DDL语句和DML语句
      executeQuery(); //执行DQL语句
      
    • 示例

      //设置可以导致SQL注入的用户名或密码
      String name = "fsfdfsg";//随意写
      String pwd = "' or '1' = '1";//sql注入
      //定义sql
      String sql = "select * from user where name = ? and pwd = ?";
      //获取pstmt对象
      PreparedStatement pstmt = conn.prepareStatement(sql);
      //设置 ? 的值
      pstmt.setString(1, name);
      pstmt.setString(2, name);
      //执行sql
      ResultSet rs = pstmt.executeQuery();
      //判断是否成功
      if(rs.next()) System.out.println("登录成功");
      else System.out.println("登录失败");
      
  • PreparedStatement原理

    PreparedStatement实际上是通过对敏感字符进行转义来防止SQL注入

    • 开启预编译

      String url = "jdbc:mysql://localhost:3306/test?useSSL=false&useServerPrepStmts=true";
      
    • PreparedStatement的好处

      • 能够防止SQL注入
      • 能够进行预编译:连接数据库时,数据库会依次进行 检查SQL语法,编译SQL,执行SQL;开启预编译后,可以一次编译多次执行,提供效率

1.4 数据库连接池和Druid

数据库连接池

数据库连接池是个容器,负责分配、管理数据库连接(Connection)
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏

好处:

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏:有的客户端占用连接但长期不使用,连接池会对其进行回收;

数据库连接池实现

  • 标准接口:DataSource

    官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能:

    Connection getConnection()
    

    那么以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象。

  • 常见的数据库连接池

    • DBCP
    • C3P0
    • Druid(常用)

Druid(德鲁伊)

步骤:

  • 导入jar包 druid-1.1.12.jar
  • 定义配置文件
  • 加载配置文件
  • 获取数据库连接池对象
  • 获取连接

实现:

  • 导入jar包

  • 定义配置文件
    在src目录下新建druid.properties文件

    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/test?useSSL=false
    username=root
    password=123456
    #初始连接数量
    initialSize=5
    #最大连接数:当初始连接数量不够时,可以增大至maxActive
    maxActive=10
    #最大等待时间
    maxWait=3000
    
  • DruidDemo.java

    package org.example.druid;
    public class DruidDemo {
          
          
        public static void main(String[] args) throws Exception{
          
          
            //1. 导入jar包
            //2. 定义配置文件
            //3. 加载配置文件
            Properties prop = new Properties();
            //System.out.println(System.getProperty("user.dir"));//查看当前路径
            prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
            //4. 获取数据库连接池对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
            //5. 获取数据库连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
        }
    }
    

druid配置详解(了解)

属性 说明 建议值
url 数据库的jdbc连接地址
username 登录数据库的用户名
password 登录数据库的用户密码
initialSize 初始连接数量 10-50已足够
maxActive 最大连接数
maxWait 最大等待时间,超过连接失败,-1为无限等待 100
minEvictableIdleTimeMillis 最大空闲时间,超过后回收连接 大于防火墙超时设置
timeBetweenEvictionRunsMillis 检查空闲连接的频率,单位毫秒, 非正整数时表示不检查
minIdle 回收空闲连接时,将保证至少有minIdle个连接. 与initialSize相同
keepAlive 连接不超过minldle,空闲时间超过规定值,执行默认sql来保证存活
removeAbandoned 获取连接后,超时后强制回收 false
removeAbandonedTimeout 设置强制回收时限 大于业务运行最长时间
logAbandoned 强制回收后,是否将stack trace 记录到日志中 true
testWhileIdle 分配连接时是否先检查该连接是否有效。(高效) true
validationQuery 检查池中的连接是否仍可用的 SQL 语句
testOnBorrow 程序 申请 连接时,进行连接有效性检查(低效) false
testOnReturn 程序 返还 连接时,进行连接有效性检查(低效) false
poolPreparedStatements 开启池的prepared true
maxPoolPrepareStatementPerConnectionSize 每个连接最多缓存多少个SQL 20
filters 配置插件 stat,wall,slf4j
connectProperties 连接属性。如设置一些连接池统计方面的配置

1.5 增删改查

//1. 查询所有
String sql = "select * from tb_brand;";
//2. 增加
String sql = "insert into tb_brand(brand_name, company_name, ordered, description, status) values(?,?,?,?,?);";//增
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,brandName);
pstmt.setString(2,companyName);
pstmt.setInt(3,ordered);
pstmt.setString(4,description);
pstmt.setInt(5,status);
//3. 修改
String sql = " update tb_brand\n" +
                "         set brand_name  = ?,\n" +
                "         company_name= ?,\n" +
                "         ordered     = ?,\n" +
                "         description = ?,\n" +
                "         status      = ?\n" +
                "     where id = ?";
//删除
String sql = " delete from tb_brand where id = ?";

2. Maven

Apache Maven是专门用于管理和构建Java项目的工具,它的主要功能有:

  • 提供了一套标准化的项目结构

  • 提供了一套标准化的构建流程(编译,测试,打包,发布……)

  • 提供了一套依赖管理机制:将导入jar包的模式变为添加依赖模式

2.1 Maven模型

在这里插入图片描述

  • 项目对象模型 (Project Object Model)

    项目对象模型就是将自己抽象成一个对象模型,有自己专属的坐标,如下图所示

  • 依赖管理模型(Dependency)

    依赖管理模型则是使用坐标来描述当前项目依赖哪儿些第三方jar包
    在这里插入图片描述

    • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.alibaba)
    • artifactId:定义当前Maven项目名称(通常是模块名称,例如 druid)
    • version:定义当前项目版本号

    依赖可以去mvnrepository官网查找

  • 插件(Plugin)

2.2 Maven仓库

仓库分类

  • 本地仓库:自己计算机上的一个目录

  • 中央仓库:由Maven团队维护的全球唯一的仓库

    • 地址: https://repo1.maven.org/maven2/
  • 远程仓库(私服):一般由公司团队搭建的私有仓库

jar包查找顺序

  • 没有远程仓库时:本地仓库 --> 中央仓库下载
  • 有远程仓库时:本地仓库 --> 远程仓库下载 --> 中央仓库下载

2.3 Maven的安装配置

Maven下载安装

  • 下载和解压

    下载地址:https://maven.apache.org/download.cgi
    在这里插入图片描述

    解压后即安装完成

  • 配置环境变量

    • 新建一个系统变量
      在这里插入图片描述

    • 在系统变量Path下,新建
      在这里插入图片描述

    • 在cmd窗口,检查配置是否成功

      mvn -version 
      

Maven配置本地仓库

  • 默认的本地仓库:
    在这里插入图片描述

  • 修改本地仓库地址
    修改maven安装路径下 conf/settings.xml 中的 < localRepository> 为一个指定目录作为本地仓库,用来存储jar包
    在这里插入图片描述

  • 配置阿里云私服
    在这里插入图片描述

IDEA配置Maven

  • 默认仓库配置
    在这里插入图片描述

  • 自定义仓库配置
    在这里插入图片描述

  • IDEA导入Maven项目
    找到要导入的项目的pom.xml文件即可导入
    在这里插入图片描述

以上配置只是为单个项目配置maven仓库,如果想要以后创建的项目都使用本地仓库,可以如下设置
在这里插入图片描述

其他步骤和前面一样

2.4 Maven常用命令

生命周期:

同一套生命周期内,执行后边的命令,前面的所有命令会自动执行。例如默认(default)生命周期如下:

在这里插入图片描述

还有其他生命周期对应命令,但一般只用到上面4个

常用命令:

  • compile :编译

  • clean:清理

  • test:测试

  • package:打包

  • install:安装

命令演示:

  • 准备:新建一个maven项目如下

    • 项目结构
      在这里插入图片描述

    • pom.xml

      <!--导入 mysql 驱动jar包-->
      <dependencies>
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.32</version>
          </dependency>
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>druid</artifactId>
              <version>1.1.12</version>
          </dependency>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.13</version>
              <scope>test</scope>
          </dependency>
      </dependencies>
      
    • HelloWorld.java

      package org.example;
      public class HelloWorld {
              
              
          public static void main(String[] args) {
              
              
              System.out.println("Hello Maven~");
          }
      }
      
    • TestMaven.java

      package org.example;
      import org.junit.Test;
      public class TestMaven {
              
              
          @Test
          public void test(){
              
              
              System.out.println("maven test ===>");
          }
      }
      
      
  • compile命令
    在pom.xml目录下,shift+右键,打开PowerShell命令窗口

    mvn compile
    

    在这里插入图片描述

    会将需要的jar包从之前配置的中央仓库下载到配置的本地仓库local_repository中
    可以看到maven-project/target/classes/org/example下生成了一个HelloWorld.class文件

  • clean命令

    mvn clean
    

    删除了target目录,并不是删除之前下载的jar包

  • package命令

    mvn package
    

    将当前项目打成的jar包,这个jar包放在target目录下
    在这里插入图片描述

  • test命令

    mvn test
    

    执行TestMaven.java

  • install命令

    mvn install
    

    将package打包的自身jar包,安装到本地仓库中去,在org/example/maven-project

    注意:以上命令都会自动下载插件,如intall命令会下载install相关插件

IDEA执行Maven命令:

  • Maven面板
    在这里插入图片描述

  • Maven Helper插件
    File --> Settings --> Plugins,搜索Maven Helper并安装
    此时右击Maven模板,可以发现有个 Run Maven 选项用以执行Maven命令

2.5 依赖管理

  • 依赖范围 scope

    在这里插入图片描述

    • compile :作用于编译环境、测试环境、运行环境。默认值
    • test : 作用于测试环境。典型的就是junit坐标,以后使用junit时,都会将scope指定为该值
    • provided :作用于编译环境、测试环境。我们后面会学习 servlet-api ,在使用它时,必须将 scope 设置为该值,不然运行时就会报错
    • runtime : 作用于测试环境、运行环境。jdbc驱动一般将 scope 设置为该值,不设置也没有任何问题
    • system:作用于编译环境、测试环境

3. MyBatis

  • MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
  • 持久层:操作数据库的Java代码作为持久层,Mybatis就是对jdbc代码进行了封装

3.1 MyBatis快速入门

配置文件 .xml .properties 都放在resources目录下

  • pom.xml:导入需要的依赖

    <dependencies>
        <!--mybatis 依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!--mysql 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!--junit 单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!-- 添加slf4j日志api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.20</version>
        </dependency>
        <!-- 添加logback-classic依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- 添加logback-core依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
    
  • mybatis-config.xml:核心配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--
            typeAliases取别名对应于Mapper.xml中的resultType属性
            正常情况下写法是:resultType="org.example.pojo.User"
            加上别名后,就可以简写为:resultType="user"
        -->
        <typeAliases>
            <package name="org.example.pojo"/>
        </typeAliases>
    
        <!--
        environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
        -->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!--数据库连接信息-->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
    
            <environment id="test">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!--数据库连接信息-->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--加载sql映射文件-->
            <mapper resource="UserMapper.xml"/>
        </mappers>
    </configuration>
    

    注意2个地方:dataSourcemappers
    配置时,标签要写按顺序写

  • UserMapper.xml:SQL映射文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="test">
        <select id="selectAll" resultType="org.example.pojo.User">
            select * from tb_user;
        </select>
    </mapper>
    

    这里的namespace没有讲究,但后面的Mapper代理开发这里不能再随意取名

  • MyBatisDemo.java:应用

    public class MyBatisDemo {
          
          
        public static void main(String[] args) throws IOException {
          
          
            //1. 加载MyBatis核心配置文件,获取 SqlSessionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //2. 获取SqlSessionduix
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //3. 执行SQL
            List<User> userList = sqlSession.selectList("test.selectAll");//命令空间.id
            System.out.println(userList);
            //4. 释放资源
            sqlSession.close();
        }
    }
    //User类是根据数据库表编写的实体类
    

在老版本的IDEA里面,mapper里面的sql语句可能爆红,这是因为与数据库连接尚未建立,IDEA不识别表名,这可以通过在IDEA中配置数据库连接完成

在右方的 database 中,+号 --> Data Source --> MySql 进行配置
在这里插入图片描述
第一次可能需要下载Driver驱动,完成之后可以在IDEA中操作mysql,相当于workbench

3.2 Mapper代理开发

  • UserMapper接口

    必要准备:创建并将UserMapper接口和UserMapper.xml生成在同一个文件夹下
    通常会将所有的Mapper接口放在一个mapper文件夹中,为了保证路径读取正常,Mapper.xml应当放在相同路径,如:
    在这里插入图片描述

    使用mvn compile命令编译后,可以看到生成的target中,可以看到UserMapper.java和UserMapper.xml在同一个位置

    在resources中应当是用 org/example/mapper 方式创建Directory,再创建 UserMapper.xml

    public interface UserMapper {
          
          
        List<User> selectAll();
    }
    
  • mybatis-config.xml配置

    <mappers>
        <!--加载sql映射文件-->
        <mapper resource="org/example/mapper/UserMapper.xml"/>
    </mappers>
    

    如果有多个Mapper.xml还可以写成

    <mappers>
        <package name="org/example/mapper"/>
    </mappers>
    

    即扫描该目录下的所有配置文件

  • UserMappr.xml配置

    <mapper namespace="org.example.mapper.UserMapper">
        <select id="selectAll" resultType="org.example.pojo.User">
            select * from tb_user;
        </select>
    </mapper>
    

    注意namespace一定要和接口同名

  • MyBatisDemo.java应用

    //1. 加载MyBatis核心配置文件,获取 SqlSessionFactory 对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSessionduix
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取UserMapper接口的代理对象,并执行SQL
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.selectAll();
    System.out.println(userList);
    //4. 释放资源
    sqlSession.close();
    

    这样做看似复杂,但对于有多个方法时,使用userMapper.selectAll();能更加遍历

3.3 MyBatis插件 - MyBatisX

在这里插入图片描述
可以自动在Mapper.xml生成对应select标签

3.4 例子:配置文件完成增删改查

开发中主要是编写Mapper接口和Mapper.xml文件

查询:resultMap 别名

别名:解决实体类和数据库表名称不一致的问题

假设User.java中的一个属性是userId,它需要与数据库表中的id属性列对应
此时由于名称不一致,将无法实现自动封装,所有的userId都为null——可以通过取别名的方式解决

//UserMapper.xml
<mapper namespace="org.example.mapper.UserMapper">
    <select id="selectAll" resultType="org.example.pojo.User">
        select id as userId, username, password, gender, addr from tb_user;
    </select>
</mapper>

缺点:每次写sql语句的时候都需要定义别名
方法:定义sql片段

<mapper namespace="org.example.mapper.UserMapper">
    <sql id="alias">
        id as userId, username, password, gender, addr
    </sql>
    <select id="selectAll" resultType="org.example.pojo.User">
        select
            <include refid="alias"/>
        from tb_user;
    </select>
</mapper>

缺点:不够灵活,例如如果只需要 id as userId, username 部分,就只能再重新写
方法:resultMap(常用)

<mapper namespace="org.example.mapper.UserMapper">
    <resultMap id="userResultMap" type="org.example.pojo.User">
        <result column="id" property="userId"/>
    </resultMap>
    <select id="selectAll" resultMap="userResultMap">
        select * from tb_user;
    </select>
</mapper>

查询:传参和占位符

参数占位符

  • #{}:会将其替换为 ? ,防止sql注入;在传递参数的时候使用
  • ${}:单纯拼接sql,会存在sql注入问题;在表名或列名不固定的时候
  • UserMapper.xml配置

    使用 #{id} 传参

    <mapper namespace="org.example.mapper.UserMapper">
        <select id="selectById" resultType="org.example.pojo.User">
            select * from tb_user where id = #{
          
          id};
        </select>
    </mapper>
    

    **特殊字符问题:**where id = #{id} 变成 where id < #{id}将会报错

    • 方法一:转义字符

      select * from tb_user where id &lt; #{
              
              id};
      
    • 方法二:CDATA

      select * from tb_user where id
      <![CDATA[
      	<
      ]]>
      #{
              
              id};
      

      输出CD+回车即可自动生成

  • UserMapper.java接口

    public interface UserMapper {
          
          
        User selectById(int id);
    }
    
  • MyBatisDemo.java应用

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    int id = 1;
    User user = userMapper.selectById(id);
    System.out.println(user);
    

查询:多参数查询

需求:根据id,username,addr三个参数查询

  • 方法一:@Param

    UserMapper接口

    List<User> selectByCondition(@Param("id") int id, @Param("username") String username, @Param("addr") String addr);
    

    UserMapper.xml

    <mapper namespace="org.example.mapper.UserMapper">
        <select id="selectByCondition" resultType="org.example.pojo.User">
            select * from tb_user where id = #{
          
          id} and username like #{
          
          username} and addr like #{
          
          addr}
        </select>
    </mapper>
    

    MyBatisDemo.java

    //接收参数
    int id = 2;
    String username = "李";
    String addr = "津";
    //处理参数
    username = username + "%";//模糊查询:姓李的
    addr = "%"+addr+"%";//模糊查询:地址中带 津 的
    //获取数据
    List<User> userList = userMapper.selectByCondition(id, username, addr);
    System.out.println(userList);
    
  • 方法二:封装成对象

    UserMapper接口

    List<User> selectByCondition(User user);
    

    UserMapper.xml:不用修改

    <mapper namespace="org.example.mapper.UserMapper">
        <select id="selectByCondition" resultType="org.example.pojo.User">
            select * from tb_user where id = #{
          
          id} and username like #{
          
          username} and addr like #{
          
          addr}
        </select>
    </mapper>
    

    MyBatisDemo.java

    //接收参数
    int id = 2;
    String username = "李";
    String addr = "津";
    //处理参数
    username = username + "%";//模糊查询:姓李的
    addr = "%"+addr+"%";//模糊查询:地址中带 津 的
    //封装对象
    User user = new User();
    user.setId(id);
    user.setUsername(username);
    user.setAddr(addr);
    //获取数据
    List<User> userList = userMapper.selectByCondition(user);
    System.out.println(userList);
    
  • 方法三:Map集合

    UserMapper接口

    List<User> selectByCondition(Map map);
    

    UserMapper.xml:不用修改

    <mapper namespace="org.example.mapper.UserMapper">
        <select id="selectByCondition" resultType="org.example.pojo.User">
            select * from tb_user where id = #{
          
          id} and username like #{
          
          username} and addr like #{
          
          addr}
        </select>
    </mapper>
    

    MyBatisDemo.java

    //接收参数
    int id = 2;
    String username = "李";
    String addr = "津";
    //处理参数
    username = username + "%";//模糊查询:姓李的
    addr = "%"+addr+"%";//模糊查询:地址中带 津 的
    //设置Map
    Map map = new HashMap<>();
    map.put("id", id);
    map.put("username", username);
    map.put("addr", addr);
    //获取数据
    List<User> userList = userMapper.selectByCondition(map);
    System.out.println(userList);
    

查询:动态条件查询

在多参数查询中,用户必须输入3个参数才能查询,但更多时候用户只给出一个,上面的写法就显得不够灵活

MyBatis可以很方便的实现动态SQL
只需要对sql语句进行修改

//UserMapper.xml
<mapper namespace="org.example.mapper.UserMapper">
    <select id="selectByCondition" resultType="org.example.pojo.User">
        select * from tb_user
        <where>
            <if test="id != null">and id = #{
    
    id}</if>
            <if test="username != null and username != ''">and username like #{
    
    username}</if>
            <if test="addr != null and addr != ''">and addr like #{
    
    addr}</if>
        </where>
    </select>
</mapper>

< where>标签用来解决多余的 “and” 问题

添加:提交事务

UserMapper接口

void add(User user);

UserMapper.xml

<mapper namespace="org.example.mapper.UserMapper">
    <insert id="add">
        insert into tb_user (id, username, password, gender, addr)
        values (#{
    
    id}, #{
    
    username}, #{
    
    password}, #{
    
    gender}, #{
    
    addr});
    </insert>
</mapper>

MyBatisDemo.java

//接收参数
int id = 4;
String username = "赵六";
String password = "123456";
String gender = "女";
String addr = "天津";
//封装对象
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
user.setGender(gender);
user.setAddr(addr);
//获取数据
userMapper.add(user);
//需要提交事务!!
sqlSession.commit();
//或者可以在获取SqlSession时设置为自动提交:SqlSession sqlSession = sqlSessionFactory.openSession(true);

添加:主键返回

在数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)

例如:添加到订单表,订单的主键自增长;同时还要添加订单项表,订单项表要根据订单的主键添加
在这里插入图片描述

只需要设置 useGeneratedKeyskeyProperty 两个值可以

//UserMapper.xml
<mapper namespace="org.example.mapper.UserMapper">
<!--    useGeneratedKeys获取主键,keyProperty指定主键对应属性值id-->
    <insert id="add" useGeneratedKeys="true" keyProperty="id">
        insert into tb_user (id, username, password, gender, addr)
        values (#{
    
    id}, #{
    
    username}, #{
    
    password}, #{
    
    gender}, #{
    
    addr});
    </insert>
</mapper>

此后id值自动放在参数之中

//获取数据
userMapper.add(user);
System.out.println(user.getId());

修改

  • 修改全部字段

    int update(User user);
    
    <mapper namespace="org.example.mapper.UserMapper">
        <update id="update">
            update tb_user
            set
                username = #{
          
          username},
                password = #{
          
          password},
                gender = #{
          
          gender},
                addr = #{
          
          addr}
            where id = #{
          
          id};
        </update>
    </mapper>
            
    int count = userMapper.update(user);//可以自动获取影响的行数
    System.out.println(count);
    
  • 修改部分字段

    如果不设置if,那么默认缺失值为null

    <mapper namespace="org.example.mapper.UserMapper">
        <update id="update">
            update tb_user
            <set>
                <if test="username != null and username != ''">username = #{
          
          username},</if>
                <if test="password != null and password != ''">password = #{
          
          password},</if>
                <if test="gender != null and gender != ''">gender = #{
          
          gender},</if>
                <if test="addr != null and addr != ''">addr = #{
          
          addr}</if>
            </set>
            where id = #{
          
          id};
        </update>
    </mapper>
    

    set字段用以解决多余的 , and 问题

删除

  • 删除一个

    int deleteById(int id);
    
    <mapper namespace="org.example.mapper.UserMapper">
        <delete id="deleteById">
            delete from tb_user where id = #{
          
          id};
        </delete>
    </mapper>
    
  • 批量删除

    • 使用注解

      int deleteByIds(@Param("ids") int[] ids);
      
      <mapper namespace="org.example.mapper.UserMapper">
      <!--    MyBatis会将数据参数封装成为一个Map集合-->
      <!--        * 默认:array = 数组-->
      <!--        * 使用@Param注解改变Map集合的名称-->
          <delete id="deleteByIds">
              delete from tb_user where id
              in
                  <foreach collection="ids" item="id" separator="," open="(" close=")">
                      #{
              
              id}
                  </foreach>
                  ;
          </delete>
      </mapper>
      

      collection表示数组,item表示列名,separator表示id直接的分隔符,open和close对应于 in 的 ()

    • 使用array

      int deleteByIds(int[] ids);
      
      <mapper namespace="org.example.mapper.UserMapper">
          <delete id="deleteByIds">
              delete from tb_user where id
              in
                  <foreach collection="array" item="id" separator="," open="(" close=")">
                      #{
              
              id}
                  </foreach>
                  ;
          </delete>
      </mapper>
      

3.5 MyBatis参数传递原理

多个参数

  • 多个参数使用方法

    User select(@Param("username") String username, @Param("password") String password);
    

    在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解时有以下命名规则:

    • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:

      map.put(“arg0”,参数值1);

      map.put(“arg1”,参数值2);

    • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:

      map.put(“param1”,参数值1);

      map.put(“param2”,参数值2);

    也就是说在没有设置@Param的情况下,下面语句也可以顺利取出数据

    select * from tb_user where username = #{
          
          arg0} and password = #{
          
          arg1};
    //或者
    select * from tb_user where username = #{
          
          param1} and password = #{
          
          param2};
    
  • 原理
    MyBatis提供了ParamNameResolver类对参数进行封装

    //双击shift,搜索ParamNameResolver类,里面有一个getNamedParams方法,用于将参数封装成为Map集合对象
    public class ParamNameResolver {
          
          
        ...
        public Object getNamedParams(Object[] args) {
          
          
            int paramCount = this.names.size();
            if (args != null && paramCount != 0) {
          
          
                //参数不为空
                if (!this.hasParamAnnotation && paramCount == 1) {
          
          
                    //单个参数情况
                    Object value = args[(Integer)this.names.firstKey()];
                    return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
                } else {
          
          
                    //多个参数情况
                    Map<String, Object> param = new MapperMethod.ParamMap();
                    int i = 0;
    
                    for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
          
          
                        Map.Entry<Integer, String> entry = (Map.Entry)var5.next();
                        param.put(entry.getValue(), args[(Integer)entry.getKey()]);
                        String genericParamName = "param" + (i + 1);
                        if (!this.names.containsValue(genericParamName)) {
          
          
                            param.put(genericParamName, args[(Integer)entry.getKey()]);
                        }
                    }
    
                    return param;
                }
            } else {
          
          
                return null;
            }
        }
        ...
    }
    
    • 不使用@Param下
      单点调试可以看到
      在这里插入图片描述
      2个参数,进行4次put,最终获得的键值对存放在param里面
      即Map为

      map.put(“arg0”,参数值1);

      map.put(“arg1”,参数值2);

      map.put(“param1”,参数值1);

      map.put(“param2”,参数值2);

    • 使用@Param之后:替换arg0, arg1 …

      map.put(“username”,参数值1);

      map.put(“password”,参数值2);

      map.put(“param1”,参数值1);

      map.put(“param2”,参数值2);

单个参数

可以通过单点调试,在getNamedParam()里面调用了wrapToMapIfCollection()方法

  • POJO 类型:直接使用。要求 属性名参数占位符名称(即#{}里面的内容) 一致

  • Map 集合类型:直接使用。要求 map集合的键名参数占位符名称 一致

  • Collection 集合类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,collection集合);

    map.put(“collection”,collection集合;

    可以使用 @Param 注解替换map集合中默认的 arg 键名

  • List 集合类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,list集合);

    map.put(“collection”,list集合);

    map.put(“list”,list集合);

    可以使用 @Param 注解替换map集合中默认的 arg 键名

  • Array 类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,数组);

    map.put(“array”,数组);

    可以使用 @Param 注解替换map集合中默认的 arg 键名

  • 其他类型:比如int类型,参数占位符名称 叫什么都可以。尽量做到见名知意

3.6 注解开发

@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);

注解用于替换配置文件,使用注解后就不必再写对应的statement了(不要删除xml配置文件)

注解用于映射简单语句,xml配置文件映射复杂功能

  • 查询 :@Select
  • 添加 :@Insert
  • 修改 :@Update
  • 删除 :@Delete

猜你喜欢

转载自blog.csdn.net/widsoor/article/details/128505872