MyBatis

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层

持久层:指的就是持久化操作的层,通常指数据访问层(dao),是用来操作数据库的
在这里插入图片描述
简而言之:MyBatis是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具

前期准备工作

依赖配置

1.创建springboot工程

2.要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

SpringBoot 3.X对应MyBatis版本为3.X

对应版本的信息可参考官方网站:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
3. 导入mysql驱动的依赖

<dependency>
 <groupId>com.mysql</groupId>
 <artifactId>mysql-connector-j</artifactId>
 <scope>runtime</scope>
</dependency>

4.配置数据库连接字符串
Mybatis中要连接数据库,需要数据库相关参数配置
application.yml:

spring:
  application:
    name: demo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java113?characterEncoding=utf8&useSSL=false// 其中java113是我的数据库名
    username: root //登录名
    password: 123456//密码
    driver-class-name: com.mysql.cj.jdbc.Driver

application.properties:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java113?
characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

5.打印日志
在配置文件中进行以下配置
application.yml:

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

application.properties:

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

6.配置驼峰自动转换

数据准备

创建用户表,并创建对应的实体类User

DROP DATABASE IF EXISTS java113;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
USE java113;
-- 创建表 
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `username` VARCHAR ( 127 ) NOT NULL,
 `password` VARCHAR ( 127 ) NOT NULL,
 `age` TINYINT ( 4 ) NOT NULL,
 `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',
 `phone` VARCHAR ( 15 ) DEFAULT NULL,
 `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
 PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 
-- 添加用户信息 
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' )

创建对应的实体类UserInfo

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

单元测试

测试类上添加了注解@SpringBootTest,该测试类在运行时,就会自动加载Spring的运行环境.
我们通过@Autowired这个注解,注入我们要测试的类,就可以开始进行测试了
在这里插入图片描述

使⽤Idea自动生成测试类

在这里插入图片描述
勾选需要生成的测试类,一路ok就可生成对应的测试类
在这里插入图片描述
Mybatis的开发有两种方式:

  1. 注解
  2. XML

Mybatis的基础操作(注解)

@Select 实现查询功能

参数传递

查找id为4的用户信息
sql语句:

select * from user_info where id=4

 @Select("select id,username,password,age,gender,phone,delete_flag,update_time from user_info where id=4")
    UserInfo selectById();

SQL语句中的id值不能写成固定数值,需要变为动态的数值
解决方案:使用#{} 的方式获取方法中的参数

@Select("select* from user_info where id=#{id}")
    UserInfo  selectAllById(Integer id);

测试用例:

   @Test
    void selectById() {
        System.out.println(userInfoMapper.selectAllById(1));
    }

在这里插入图片描述

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样) 这种SQL我们称之为"预编译SQL"

为了使数据库的查询结果和返回值类型中的属性能够自动匹配,通常会对 MySQL 数据库和 JavaBean 采用同一套命名规则,即 Java 命名驼峰规则,这样就不需要再做映射了(数据库表字段名和属性名不一致时需要手动映射)。
在这里插入图片描述
查询结果中字段不一致的为null,没有赋值成功

在这里插入图片描述

和数据库中的字段不一致,需要映射
方法有:
1.起别名
2.结果映射
3.驼峰转换

起别名

和mysql一样通过as关键字

 
 @Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, "
            +"create_time as createTime, update_time as updateTime from user_info")
   List<UserInfo> queryAllUser();
结果映射
    @Select("select id, username, `password`, age, gender, phone, delete_flag,"
            +"create_time , update_time  from user_info")
    @Results(id = "resultMap",value = {
            @Result(column = "delete_flag", property = "deleteFlag"),
            @Result(column = "update_time", property = "updateTime"),
            @Result(column = "create_time", property = "createTime"),
        }
    )
    List<UserInfo> queryAllUser2();

@Results 各个属性的含义:
id:表示当前结果集声明的唯一标识;

value:表示结果集映射关系;

@Result:代表一个字段的映射关系。其中,column 指定数据库字段的名称,property 指定实体类属性的名称,jdbcType 数据库字段类型,id 为 true 表示主键,默认 false。

如果其他SQL,也希望可以复用这个映射关系,可以给这个Results定义⼀个名称

@ResultMap 来引用映射结果集
 @Select("select* from user_info where id=#{id}")
    @ResultMap(value = "resultMap")
    UserInfo  selectAllById(Integer id);

这样不需要每次声明结果集映射时都复制冗余代码,简化开发,提高了代码的复用性。

驼峰转换

在配置文件中添加配置

mybatis:
  configuration:
	map-underscore-to-camel-case: true #配置驼峰自动转换

驼峰命名规则:abc_xyz=>abcXyz

• 表中字段名:abc_xyz
• 类中属性名:abcXyz

@Param 映射多个参数

设置参数的别名,如果使用 @Param 设置别名,#{}里面的属性名必须和@Param 设置的⼀样

@Select("select username, `password`, age, gender, phone from user_info where 
id= #{userid} ")
UserInfo queryById(@Param("userid") Integer id);

@Insert 实现插入功能

插入一条用户信息

使用@Insert注解

    @Insert({
         "insert into user_info(id,username,`password`,age,gender) VALUES(#{id},#{username},#{password},#{age},#{gender})"
    })
     Integer InsertUser(UserInfo userInfo);

测试用例:

    @Test
    void insertUser() {
        UserInfo userInfo=new UserInfo();
        userInfo.setId(10);
        userInfo.setAge(16);
        userInfo.setUsername("cyn");
        userInfo.setGender(0);
        userInfo.setPassword("132456");
        Integer result=userInfoMapper.InsertUser(userInfo);
        System.out.println("影响行数:"+result+" id:"+userInfo.getId() );
    }

在这里插入图片描述

如果设置了 @Param 属性,#{}需要使参数.属性来获取

    @Insert({
            "insert into " +
            "user_info(id,username,`password`,age,gender)"
            +"VALUES(#{userInfo.id},#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})"
    })    Integer insertUser2(@Param("userInfo")UserInfo userInfo);

Insert语句默认返回的是受影响的行数
但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插⼊数据的id

比如订单系统
当我们下完订单之后,需要通知物流系统,库存系统,结算系统等,这时候就需要拿到订单ID

@Options 获取自增id的值

@Options (useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
  useGeneratedKeys = true
    -true 自动递增 默认为false
- keyProperty = "id"
    -id 这个id为实体类中的id
- keyColumn = "id"
  	-id 这个id为数据库中的字段id

在这里插入图片描述

设置useGeneratedKeys=true 之后,方法返回值依然是受影响的行数
自增id 会设置在上述 keyProperty 指定的属性中.

@Update 实现更新功能

更新用户id为5的名字为张三

Mapper接口

@Update("UPDATE user_info set username=#{username} where id=#{id}")
    void update(UserInfo userInfo);

测试用例

 @Test
    void update() {
        UserInfo userInfo=new UserInfo();
        userInfo.setId(5);
        userInfo.setUsername("张三");
        userInfoMapper.update(userInfo);
    }

在这里插入图片描述

@Delete 实现删除功能

根据id删除用户信息

 @Delete("DELETE from user_info where id=#{id}")
    void deleteUser(Integer id);

测试用例

   @Test
    void deleteUser() {
        userInfoMapper.deleteUser(10);
    }

在这里插入图片描述

XML方式

MyBatisXML的方式需要以下两步:

  1. 配置数据库连接字符串和MyBatis
  2. 写持久层代码

配置连接字符串和MyBatis

spring:
  application:
    name: demo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java113?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 9090
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

持久层代码

在这里插入图片描述
数据持久成的实现,MyBatis的固定xml格式:

<?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="com.example.demo.mapper.UserInfoMapperXML">

其中namespace要写成mapper接口的路径

创建UserInfoXMLMapper.xml
在这里插入图片描述
classpath:是resource根路径
路径参考yml中的配置

使用插件MybatisX辅助开发
在这里插入图片描述

@Mapper
public interface UserInfoMapperXML {
    List<UserInfo> selectAll();
}

可在.xml生成对应的语句
在这里插入图片描述
快捷键 Alt+enter

在.xml自动生成


<select id="selectAll2" resultType="com.example.demo.model.UserInfo"></select>

select查询标签:是用来执行数据库的查询操作的:
◦ id :是和Interface (接⼝)中定义的方法名称⼀样的,表示对接口的具体实现方法。
◦ resultType :是返回的数据类型,也就是开头我们定义的实体类.

增删改查

< insert>

UserInfoMapperXML接口

    Integer insertUser(UserInfo userInfo);

UserInfoXMLMapper.xml实现

   <insert id="insertUser">
        insert into user_info(username,password,age) values (#{username},#{password},#{age})
    </insert>

测试用例

    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("username1");
        userInfo.setPassword("password1");
        userInfo.setAge(18);
        System.out.println("影响行数:"+userInfoMapperXML.insertUser(userInfo));
    }

在这里插入图片描述

使用@Param设置参数名称

  Integer insertUser2(@Param("userInfo") UserInfo userInfo);
  <insert id="insertUser2" useGeneratedKeys="true" keyProperty="id">
        insert into user_info(username,password,age) values (#{userInfo.username},#{userInfo.password},#{userInfo.age})
    </insert>

返回自增id
在select标签中添加参数

useGeneratedKeys="true" keyProperty="id"

测试用例

@Test
    void insertUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("username5");
        userInfo.setPassword("password5");
        userInfo.setAge(18);
        Integer result = userInfoMapperXML.insertUser2(userInfo);
        System.out.println("影响行数:"+result+",自增id:"+userInfo.getId());
    }

在这里插入图片描述

< delete>

根据id删除用户信息

UserInfoMapperXML接口

      Integer deleteUser(Integer id);

UserInfoXMLMapper.xml实现


    <delete id="deleteUser">
        delete from user_info where id=#{id}
    </delete>

测试用例

 @Test
    void deleteUser() {
        userInfoMapperXML.deleteUser(28);
    }

在这里插入图片描述

< update>

UserInfoMapperXML接口

void updateUser(UserInfo userInfo);

UserInfoXMLMapper.xml实现

    <update id="updateUser">
        INSERT INTO user_info (
        username,
        `password`,
        age,
  		gender,
        phone)
        VALUES (
        #{username},
        #{age},
    	#{gender},
        #{phone})

    </update>

数据库在这里插入图片描述
测试用例

    @Test
    void updateUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(25);
        userInfo.setGender(1);
        userInfo.setUsername("update111");
        userInfo.setId(14);
        userInfo.setPhone("2456444444");
        userInfoMapperXML.updateUser(userInfo);
    }

在这里插入图片描述
更新结果
在这里插入图片描述

< select>

UserInfoMapperXML接口

    List<UserInfo> selectAll();

UserInfoXMLMapper.xml实现


    <sql id="allColumn">
        id, username, age, gender, phone, delete_flag, create_time, update_time
    </sql>
   <select id="selectAll" resultType="com.example.demo.model.UserInfo">
        SELECT
        <include refid="allColumn"></include>
        FROM `user_info`
        </select>

测试用例

 @Test
    void selectAll() {
        userInfoMapperXML.selectAll().stream().forEach(x-> System.out.println(x));
    }

对于字段不一致的问题和注解的解决方式一致
可以进行驼峰转换(推荐)
介绍一下结果映射

<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
 <id column="id" property="id"></id>
 <result column="delete_flag" property="deleteFlag"></result>
 <result column="create_time" property="createTime"></result>
 <result column="update_time" property="updateTime"></result>
</resultMap>

复用

<select id="queryAllUser" resultMap="BaseMap">
 select id, username,`password`, age, gender, phone, delete_flag, 
create_time, update_time from user_info
</select>

多表查询

建⼀张文章表,进行多表关联查询

DROP TABLE IF EXISTS articleinfo;
CREATE TABLE articleinfo (
 id INT PRIMARY KEY auto_increment,
 title VARCHAR ( 100 ) NOT NULL,
 content TEXT NOT NULL,
 uid INT NOT NULL,
 delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 create_time DATETIME DEFAULT now(),
 update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 
);

建立实体表ArticleInfo

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    //用户相关信息
    private String username;
    private Integer age;
    private Integer gender;
}

根据uid查询作者的名称等相关信息

ArticleInfoMapper 接口:

@Mapper
public interface ArticleInfoMapper {
    @Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +
            "FROM article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id " +
            "WHERE ta.id = #{id}")
    ArticleInfo queryUserByUid(Integer id);
}

测试用例

    @Test
    void queryUserByUid() {
        articleInfoMapper.queryUserByUid(1);

    }

在这里插入图片描述

${ } 字符串替换

不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

ORDER BY ${columnName}

当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。 举个例子,如果你想 select 一个表任意一列的数据时

@Select("select * from user_info where ${column} = #{value}")
UserInfo findByColumn(@Param("column") String column, @Param("value") String value);

这种方式也同样适用于替换表名的情况。

注意:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。

对于String类型的参数

   @Select("select* from user_info where usename=${username} and gender=#{gender}")
    List<UserInfo> selectAllBynameAndGender(String username,Integer gender);

在这里插入图片描述
字符串作为参数时,需要添加引号 ‘’ ,使用 ${} 不会拼接引号 ‘’ ,导致程序报错

 @Select("select* from user_info where username='${username}' and gender=#{gender}")
    List<UserInfo> selectAllBynameAndGender(String username,Integer gender);

在这里插入图片描述
#{} 使用的是预编译SQL,通过? 占位的方式,提前对SQL进⾏编译,然后把参数填充到SQL语句中.#{} 会根据参数类型,自动拼接引号’’ .

${}会直接进行字符替换,对SQL进行编译.如果参数为字符串,需要加上引号 ’ ’ .

SQL注入

SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加⼀些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

测试用例

	@Test
    void selectAllBynameAndGender() {
        System.out.println(userInfoMapper.selectAllBynameAndGender("'or 1='1", 0));
    }

在这里插入图片描述
结果依然被正确查询出来了,其中参数or被当做了SQL语句的⼀部分

SQL注入是⼀种非常常见的数据库攻击手段, SQL注入漏洞也是网络世界中最普遍的漏洞之一.
如果发生在用户登录的场景中,密码输入为 ’ or 1='1 ,就可能完成登录(不是一定会发生的场景,需要看登录代码如何写)

sort

${}的使用场景

排序

@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);

使用$ {sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了

注意:此处sort参数为String类型,但是SQL语句中,排序规则是不需要加引号 ’ ’ 的,所以此时的${sort} 也不加引号

like 查询like

使用#{}会报错

@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);

把#{}改成$ {}可以正确查出来,但是$ {}存在SQL注入的问题,所以不能直接使用${}.
使用mysql的内置函数concat()

@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

数据库连接池

我们使用了数据库连接池技术,避免频繁的创建连接,销毁连接
在这里插入图片描述

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,而不是再重新建立⼀个
没有使用数据库连接池的情况:每次执行SQL语句,要先创建⼀个新的连接对象,然后执行SQL语句, SQL语句执行完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接比较消耗资源
使用数据库连接池的情况:程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,用户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行SQL,SQL语句执行完,再把Connection归还给连接池.
优点:

  1. 减少了网络开销
  2. 资源重用
  3. 提升了系统的性能
使用

常见的数据库连接池:
• C3P0
• DBCP
• Druid
• Hikari

  1. Hikari:SpringBoot默认使用的数据库连接池
    在这里插入图片描述

  2. Druid
    如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引相关依赖即可

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-3-starter</artifactId>
 <version>1.2.21</version>
</dependency>

参考官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
• Druid连接池是阿⾥巴巴开源的数据库连接池项目
• 功能强大,性能优秀,是Java语言最好的数据库连接池之⼀

#{}和${}区别

  1. #{}:预编译处理, ${}:字符直接替换
  2. #{}可以防止SQL注入,${}存在SQL注入的风险,查询语句中,可以使用#{},推荐使用#{}
  3. 但是⼀些场景,#{}不能完成,比如排序功能,表名,字段名作为参数时,这些情况需要使用${}
  4. 模糊查询虽然${}可以完成,但因为存在SQL注⼊的问题,所以通常使用mysql内置函数concat来完成
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值