MyBatis

一. Mybatis 是什么

MyBatis 是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库工具

二. 使用Mybatis 进行简单查询 

2.1 创建数据库和表

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使⽤数据数据
use mycnblog;

-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
 id int primary key auto_increment,
 username varchar(100) not null,
 password varchar(32) not null,
 photo varchar(500) default '',
 createtime datetime default now(),
 updatetime datetime default now(),
 `state` int default 1
) default charset 'utf8mb4';

-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
 id int primary key auto_increment,
 title varchar(100) not null,
 content text not null,
 createtime datetime default now(),
 updatetime datetime default now(),
 uid int not null,
 rcount int not null default 1,
 `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
 vid int primary key,
 `title` varchar(250),
 `url` varchar(1000),
createtime datetime default now(),
updatetime datetime default now(),
 uid int
)default charset 'utf8mb4';

-- 添加⼀个⽤户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,`createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- ⽂章添加测试数据
insert into articleinfo(title,content,uid)
 values('Java','Java正⽂',1);
 
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);

2.2 添加 MyBatis 框架支持

2.2.1 老项目中添加 MyBatis

<!-- 添加 MyBatis 框架 -->
<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.1.4</version>
</dependency>

<!-- 添加 MySQL 驱动 -->
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.38</version>
 <scope>runtime</scope>
</dependency>

也可以使用 EditStarters 插件

2.2.2 新项目中添加MyBatis

 2.3 配置连接字符串和 Mybatis

2.3.1 配置连接字符串

在application.yml 中添加以下内容:

# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

2.3.2 配置 Mybatis 中的 XML 路径

# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
 mapper-locations: classpath:mapper/**Mapper.xml

2.4 添加业务代码

 2.4.1 添加实体类

用户实体类

import java.time.LocalDateTime;
import lombok.Data;

@Data
public class Userinfo {
    private int id;
    private String username;
    private String password;
    private String photo;// 头像
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}

2.4.2 添加 mapper 接口

数据持久层的接⼝:
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface UserMapper {

 public List<User> getAll();

}

2.4.3 添加 UserMapper.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.UserMapper">
 
</mapper>
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="com.example.demo.mapper.UserMapper">
     <select id="getAll" resultType="com.example.demo.model.User">
         select * from userinfo
     </select>
</mapper>

· <mapper>标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接⼝的全限定 名,包括全包名.类名。

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

2.4.4 添加 Service

服务层代码:

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserService {
 @Resource
 private UserMapper userMapper;
 public List<User> getAll() {
 return userMapper.getAll();
 }
}

2.4.5 添加 Controller

控制层代码:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
 @Resource
 private UserService userService;

 @RequestMapping("/getall")
 public List<User> getAll(){
     return userService.getAll();
 }
}

三. 增删改操作

<insert>标签:插入语句

<update>标签:修改语句

<delete>标签:删除语句

3.1 增加用户操作

controller 实现代码:

@RequestMapping(value = "/add",method = RequestMethod.POST)
public Integer add(@RequestBody User user){
 return userService.getAdd(user);
}
mapper interface:
Integer add(User user);

mapper.xml

<insert id="add">
 insert into userinfo(username,password,photo,state)
 values(#{username},#{password},#{photo},1)
</insert>

特殊添加:返回自增 id

默认情况下返回的是受影响的行数,如果想要返回自增id,实现如下:

controller:

@RequestMapping(value = "/add2", method = RequestMethod.POST)
public Integer add2(@RequestBody User user) {
 userService.getAdd2(user);
 return user.getId();
}

mapper 接口:

@Mapper
public interface UserMapper {
 // 添加,返回⾃增id
 void add2(User user);
}

mapper.xml 实现如下:

<!-- 返回⾃增id -->
<insert id="add2" useGeneratedKeys="true" keyProperty="id">
 insert into userinfo(username,password,photo,state)
 values(#{username},#{password},#{photo},1)
</insert>

1)useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false。

2)keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

3)keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

3.2 修改用户操作

controller:

@RequestMapping("/update")
public Integer update(Integer id, String name) {
    return userService.update(id, name);
}

mapper.xml 实现代码:

<update id="update">
 update userinfo set username=#{name} where id=#{id}
</update>

3.3 删除用户操作

<delete id="delById">
    delete from userinfo where id=#{id}
</delete>

四. 查询操作

4.1 单表查询

根据用户 id 查询用户信息。

Controller 实现代码:

@RequestMapping("/getuser")
public Userinfo getUserById(Integer id) {
 return userService.getUserById(id);
}

Mapper.xml 实现代码:

<select id="getUserById" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo where id = #{id}
</select>

4.1.1 参数占位符 #{} 和 ${}

1) #{}: 预编译处理

2) ${}: 字符直接替换

预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

4.1.2 ${} 优点

<select id="getListByOrder" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo order by id ${order}
</select>
使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了,因为当使用 #{sort} 查询时,如果传递的值为 String 则会加单引号,就会导致 sql 错误。

4.1.3 SQL 注入问题

当我们使用 ${} 实现登录的 mapper.xml 时:

<select id="login" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo where username='${username}' and password='${password}'
</select>

单元测试代码如下:

输入错误密码:“' or 1='1”

 数据库中的admin用户

 运行代码

 我们发现使用错误的密码居然成功登录了,这无疑是一个巨大的漏洞

原因也不难发现:我们使用 ${} 来接收密码,使有心之人利用 ${} 的弊端改变了这条 sql 的语义

 结论:1)用于查询的字段,尽量使用 #{} 预查询的方式

            2)在不得不使用 ${} 时,一定要保证其中的参数是可穷举的,并且使用 if 进行判断,进                      而确保没有 sql 注入的风险

4.1.4 like 查询

like 使⽤ #{} 报错
<select id="getListByName" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo where username like '%#{username}%'
</select>
相当于: select * from userinfo where username like '%'username'%';
这个是不能直接使用 ${}(因为其中的参数不可穷举),可以考虑使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:
<select id="getListByName" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo where username like concat('%',#{username},'%')
</select>

4.2 多表查询

如果是增、删、改返回搜影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的。
然⽽即使是最简单查询⽤户的名称也要设置返回的类型
对于 <select> 查询标签来说至少需要两个属性:
1)id 属性:用于标识实现接口中的那个方法;
2)结果映射属性:结果映射有两种实现标签:<resultMap> 和 <resultType>

4.2.1 返回类型:resultType

绝⼤数查询场景可以使⽤ resultType 进⾏返回,如下代码所示:

<select id="getUserById" resultType="com.example.demo.entity.Userinfo">
    select * from userinfo where id = #{id}
</select>
它的优点是使⽤⽅便,直接定义到某个实体类即可

4.2.2 返回字典映射:resultMap

resultMap 使用场景:

1)字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;

2)⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。
字段名和属性名不同的情况
当数据库中的密码字段名为:password
而程序中的属性名为:pwd 时,
我们如果再使用 resultType 就会出现找不到这个属性,从而查询不到的情况。
这时就需要使用 resultMap:

 mapper.xml:

<resultMap id="BaseMap" type="com.example.demo.model.User">
 <id column="id" property="id"></id>
 <result column="username" property="username"></result>
 <result column="password" property="pwd"></result>
</resultMap>
<select id="getUserById" resultMap="com.example.demo.mapper.UserMapper.BaseMap">
 select * from userinfo where id=#{id}
</select>

4.2.3 多表查询

当我们需要提供一个书籍的 id 而查询到书籍的信息和作者的 username 时:

1)构建一个书籍信息的实体类对象:

import lombok.Data;

import java.io.Serializable;

@Data
public class Articleinfo implements Serializable {
    private final long serializableId = 1L;
    private int id;
    private String title;
    private String content;
    private String createtime;
    private String updatetime;
    private int uid;
    private int rcount;
    private int state;
}

2)再构建一个需要提供给前端的实体类对象,其中包含另一个表的部分数据:

注意:​​​这个实体类需要继承上一个实体类,并重写 toSpring 方法,lombook 不会执行父类的 toSpring 方法。

import com.example.demo.entity.Articleinfo;
import lombok.Data;

import java.io.Serializable;

@Data
public class ArticleinfoVO extends Articleinfo implements Serializable {
    private final long serializableId = 1L;
    private String username;

    @Override
    public String toString() {
        return "ArticleinfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

mapper.xml 代码 :

<select id="getById" resultType="com.example.demo.entity.vo.ArticleinfoVO">
    select a.*,u.username from articleinfo a
    left join userinfo u on u.id=a.uid
    where a.id=#{id}
</select>

单元测试:

@SpringBootTest
class ArticleMapperTest {

    @Autowired
    private ArticleMapper articleMapper;

    @Test
    void getById() {
        ArticleinfoVO articleinfoVO = articleMapper.getById(1);
        System.out.println(articleinfoVO);
    }
}

 代码执行结果:

五. 动态SQL使用

5.1 <if>标签

注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤户的时候有不确定的字段传⼊,程序应该如何实现呢?
这个时候就需要使⽤动态标签 <if> 来判断了,⽐如添加的时候头像 photo 为⾮必填字段,具体实现如下:
<insert id="insert" parameterType="org.example.model.User" useGeneratedKeys="true" keyProperty="id">
 insert into user(
         username,
         password,
         nickname,
         <if test="sex != null">
          sex,
         </if>
         birthday,
         head
     ) values (
         #{username},
         #{password},
         #{nickname},
         <if test="sex != null">
          #{sex},
         </if>
         #{birthday},
         #{head}
     )
</insert>

注意:text 中的 sex 是属性而非字段

5.2 <trim>标签

之前的插⼊⽤户功能,只是有⼀个 sex 字段可能是选填项,如果所有字段都是⾮必填项,就考虑使⽤

<trim>标签结合<if>标签,对多个字段都采取动态⽣成的⽅式。
<trim>标签中有如下属性:
        1)prefix:表示整个语句块,以prefix的值作为前缀
        2)suffix:表示整个语句块,以suffix的值作为后缀
        3)prefixOverrides:表示整个语句块要去除掉的前缀
        4)suffixOverrides:表示整个语句块要去除掉的后缀
调整 UserMapper.xml 的插⼊语句为:
<insert id="insert" parameterType="org.example.model.User" useGeneratedKeys="true" keyProperty="id">
     insert into user
       <trim prefix="(" suffix=")" suffixOverrides=",">
         <if test="username != null">
             username,
         </if>
         <if test="password != null">
             password,
         </if>
         <if test="nickname != null">
             nickname,
         </if>
         <if test="sex != null">
             sex,
         </if>
         <if test="birthday != null">
             birthday,
         </if>
         <if test="head != null">
             head,
         </if>
         <if test="createTime != null">
             create_time,
         </if>
       </trim>
       <trim prefix="values (" suffix=")" suffixOverrides=",">
         <if test="username != null">
             #{username},
         </if>
         <if test="password != null">
             #{password},
         </if>
         <if test="nickname != null">
             #{nickname},
         </if>
         <if test="sex != null">
             #{sex},
         </if>
         <if test="birthday != null">
             #{birthday},
         </if>
         <if test="head != null">
             #{head},
         </if>
         <if test="createTime != null">
             #{createTime},
         </if>
       </trim>
</insert>
在以上 sql 动态解析时,会将第⼀个 <trim> 部分做如下处理:
1)基于 prefix 配置,开始部分加上 (
2)基于 suffix 配置,结束部分加上 )
3)多个 <if>组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixO                  verrides 配置去掉最后⼀个 ,
4)注意 <if test=“createTime != null”> 中的 createTime 是传⼊对象的属性

5.3 <where>标签

传⼊的⽤户对象,根据属性做 where 条件查询,⽤户对象中属性不为 null 的,都为查询条件。如 user.username 为 "a",则查询条件为 where username="a":

UserMapper 接⼝中新增条件查询⽅法:
List<User> selectByCondition(User user);
UserMapper.xml 中新增条件查询 sql:
<select id="selectByCondition" parameterType="org.example.model.User" resultMap="BaseResultMap">
     select id, username, password, nickname, sex, birthday, head, create_time
         from user
     <where>
         <if test="username != null">
             and username=#{username}
         </if>
         <if test="password != null">
             and password=#{password}
         </if>
         <if test="nickname != null">
             and nickname=#{nickname}
         </if>
         <if test="sex != null">
             and sex=#{sex}
         </if>
         <if test="birthday != null">
             and birthday=#{birthday}
         </if>
         <if test="head != null">
             and head=#{head}
         </if>
         <if test="createTime != null">
             and create_time=#{createTime}
         </if>
     </where>
</select>

5.4 <set>标签

根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤<set>标签来指定动态内容。
UserMapper 接⼝中修改⽤户⽅法:根据传⼊的⽤户 id 属性,修改其他不为 null 的属性:
int updateById(User user);
UserMapper.xml 中添加更新⽤户 sql:
<update id="updateById" parameterType="org.example.model.User">
     update user
         <set>
             <if test="username != null">
                 username=#{username},
             </if>
             <if test="password != null">
                 password=#{password},
             </if>
             <if test="nickname != null">
                 nickname=#{nickname},
             </if>
             <if test="sex != null">
                 sex=#{sex},
             </if>
             <if test="birthday != null">
                 birthday=#{birthday},
             </if>
             <if test="head != null">
                 head=#{head},
             </if>
             <if test="createTime != null">
                 create_time=#{createTime},
             </if>
         </set>
     where id=#{id}
</update>

5.5 <foreach> 标签

对集合进⾏遍历时可以使⽤该标签。<foreach>标签有如下属性:
1)collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
2)item:遍历时的每⼀个对象
3)open:语句块开头的字符串
4)close:语句块结束的字符串
5)separator:每次遍历之间间隔的字符串
示例:根据多个⽂章 id 来删除⽂章数据。
ArticleMapper 中新增接⼝⽅法:
int deleteByIds(List<Integer> ids);
ArticleMapper.xml 中新增删除 sql:
<delete id="deleteByIds">
     delete from article
     where id in
     <foreach collection="list" item="item" open="(" close=")" separator=",">
         #{item}
     </foreach>
</delete>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值