一天快速掌握Mybaits[一]

一、搭环境

Spring Initializr的搭建

在这里插入图片描述

在这里插入图片描述

创建完毕后的项目结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CLSvNTwT-1669084188543)(E:\localhost\mybatis\image-20221102131813833.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdcrLAkQ-1669084188544)(E:\localhost\mybatis\image-20221102131911120.png)]

此时application的后缀更名为yml,因为这样,看起来更简洁明了,而作用上,无差别

数据库环境的搭建

新建数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUjMAhmT-1669084188546)(E:\localhost\mybatis\image-20221102132318426.png)]

执行SQL语句

use `mybatis-demo`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert  into `user`(`id`,`username`,`age`,`address`) values (1,'UZI',19,'上海'),(2,'PDD',25,'上海');

在这里插入图片描述

在这里插入图片描述

id设置为了主键自动递增

在这里插入图片描述

yml配置

server:
  port: 8098

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver     # msyql8 加cj mysql8以下去掉cj
    url: jdbc:mysql://localhost:3306/mybatis-demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
    username: root   # url是表示用于与mysql的进行一个连接 如果是本机 可以用localhost 如果不是要更换成ip
    password: 123456   #username表示SQL 账号 password表示密码

mybatis:
  mapper-locations: classpath:/Mapper/*.xml   #resources 目录下的 Mapper 目录下面的所有xml文件
  type-aliases-package: com.yhn.entity        #自动配置别名
  configuration:
    map-underscore-to-camel-case: true                      #开启驼峰命名
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   #配置打印SQL语句


项目结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cZG0Z0F-1669084188552)(E:\localhost\mybatis\mybatis.assets\image-20221108112353481.png)]

二、基本的CRUD

一般web开发需要这几个层面

  • Controller 控制层面 负责接收前端传过来的参数
  • Service 业务处理层 负责业务处理
  • Mapper/Dao 数据层 负责数据调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQsVu88T-1669084188556)(E:\localhost\mybatis\mybatis.assets\image-20221107235541022.png)]

结构搭建

Controller

package com.yhn.controller;

import com.yhn.entity.User;
import com.yhn.service.UserService;
import org.springframework.web.bind.annotation.*;
/**
 * CRUD
 * @Description
 * @Author YeHaoNan~
 * @Date 2/11/2022  23:37
 * @Version 1.0.0
 **/
@RestController
@RequestMapping("/user")
public class UserController {
    
    
    @Resource
    private UserService service;
}

Service

public interface UserService {
    
    
}

ServiceImpl

@Service
public class UserServiceImpl implements UserService {
    
    
    @Resource
    private UserMapper mapper;
}

Mapper

@Mapper
public interface UserMapper {
    
    
}

面试题

@RestController 是哪几个注解的复合注解

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    
    
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

/*
最主要的就是
@Controller
@ResponseBody

@Controller :
spring会遍历上面扫描出来的所有bean,过滤出那些添加了注解@Controller的bean,将Controller中所有添加了注解@RequestMapping的方法解析出来封装成RequestMappingInfo存储到RequestMappingHandlerMapping中的mappingRegistry。后续请求到达时,会从mappingRegistry中查找能够处理该请求的方法。

@ResponseBody
加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。 可以加方法上面也可以加在类上面,加在类上面的话,那么就表示所有的方法都会自动添加@RequestBody
*/

@Autowired与@Resource 的区别

1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。

2、 @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

推荐使用:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅。

查询

@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Resource
    private UserService service;

    /**
     *  查询所有
     * @author YeHaoNan~
     * @date 2/11/2022 23:37
     * @return List<User>
     */
    @GetMapping("/findAll")
    public List<User> findAll(){
    
    
        return service.findAll();
    }

    /**
     * 根据id进行查询
     * @author YeHaoNan~
     * @date 2/11/2022 23:38
     * @param id
     * @return User
     */
    @GetMapping("/findById")
    public User findById(Integer id){
    
    
        return service.findById(id);
    }
}
public interface UserService {
    
    

    /**
     *  查询所有
     * @author YeHaoNan~
     * @date 2/11/2022 23:38
     * @return List<User>
     */
    List<User> findAll();

    /**
     * 根据id进行查询
     * @author YeHaoNan~
     * @date 2/11/2022 23:38
     * @param id
     * @return User
     */
    User findById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {
    
    
    @Resource
    private UserMapper mapper;


    @Override
    public List<User> findAll() {
    
    
        return mapper.findAll();
    }

    @Override
    public User findById(Integer id) {
    
    
        return mapper.findById(id);
    }
}
@Mapper
public interface UserMapper {
    
    

    /**
     *  查询所有
     * @author YeHaoNan~
     * @date 2/11/2022   23:38
     * @return List<User>
     */
    List<User> findAll();

    /**
     * 根据id进行查询
     * @author YeHaoNan~
     * @date 2/11/2022   23:38
     * @param id
     * @return User
     */
    // @Param 注解 后面会详细讲解  在当前你可以看做不存在
    User findById(@Param("id") Integer id);
}

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.yhn.mapper.UserMapper">

    <select id="findAll" resultType="com.yhn.entity.User">
        select * from user
    </select>

    <select id="findById" resultType="com.yhn.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>

解析 : Mybatis里面 Mapper中的namespace用于绑定Dao/Mapper接口的,即面向接口编程,它的功能和Dao接口的实现类Impl相当,但是他不用写接口实现类,通过namesapce(命名空间)的绑定直接通过id找到相应方法,执行相应的SQL语句。

比如我目前写是 com.yhn.mapper.UserMapper 那么可以通俗的理解为,我这个mybatis 的映射文件是只属于 com.yhn.mapper
包下的 UserMapper 使用

如何更加明朗的看待? 可以下载一个idea 插件 MybatisX 插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2shxlITd-1669084188558)(E:\localhost\mybatis\mybatis.assets\image-20221108124201519.png)]

装上插件后的效果,蓝红鸟以线为例,namespese 对应的是包下面的接口

mybatis标签的意思

<select></select> <!--表示这个是一个SQL查询<==> SELECT-->
<insert></insert> <!--表示这个是一个SQL新增<==> INSERT-->
<update></update> <!--表示这个是一个SQL修改<==> UPDATE-->
<delete></delete> <!--表示这个是一个SQL删除<==> DELETE-->
你就可以这里理解 你现在要执行什么类型的SQL 你就使用什么样的标签

select insert update delete标签的的属性

id : 代表着 namespace绑定的那个接口的方法

resultType : 代表返回的类型 一般是返回实体类型

后续还有,后面还会继续讲解

为什么UserController 下面的 findById方法里面 有@RequestParam注解?

首先,明确一点,就算这个地方不加,也没有影响,程序依旧能跑起来,并且还能返回值,一切正常

那么? 为什么还需要加上@RequestParam注解?

在这里先介绍 @RequestParam

作用:

​ @RequestParam:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

语法:

​ @RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)

​ value:参数名

​ required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。

​ defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

加上@RequestParam 和不加@RequestParam
1.如果加上@RequestParam,
1.1 defaultValue属性可以给参数设置默认值,
1.2 required可以设置参数是否必须传,默认为true
1.3 value可以将前端传来的值的key与你用来接收值的参数进行绑定,无需在意参数名字是否一致
1.4 如果设置了defaultValue属性,那么required默认为false
2.如果不加@RequestParam
2.1 前端传来参数的key必须与你后端接受值的那个参数名一致,不然获取不到值
2.2 后端设置的参数的类型如果是基本数据类型 如 int long 等8中基本类型,并且前端没有传这个参数,那么就会报一个错误
"Optional char parameter ‘xxx’ is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type "
这个意思是说,参数xxx是可选的,但是它本身是个基本类型数据,没有办法被转换成null值,让你考虑它把变为当前基本类型的包装类如Long,Integer等,那么你把它转换成对应的包装类就可以了
2.3 后端设置的参数的类型如果是包装类或者String或者自定义的类那么,那个前端可传可不传,如果不传获取的就是null值

为什么UserMapper 下面的 findById方法里面 有@Param注解?

@Param的作用就是给参数命名,比如在mapper里面某方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值。

其实还有多种方式,但是不推荐,但是后面会提到

测试用例 此文档一律使用ApiPost 当然也可以使用其他的postman、Apifox

因为findAll 接口没有任何参数 所有请求区为空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-brZeYY1a-1669084188559)(E:\localhost\mybatis\mybatis.assets\image-20221108155039504.png)]

findById 测试 需要传入一个id 的参数并且设定值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUFccShO-1669084188560)(E:\localhost\mybatis\mybatis.assets\image-20221108155709544.png)]

新增

在UserController里面添加

 	@PostMapping("/insert")
    public void insert(@RequestBody User user){
    
    
        service.insert(user);
    }

在UserService添加

/**
     * 新增一条数据
     * @author YeHaoNan~
     * @date 2/11/2022 23:40
     * @param user
     */
    void insert(User user);

在UserServiceImpl添加

  @Override
    public void insert(User user) {
    
    
        mapper.insert(user);
    }

在UserMapper添加

void insert(@Param("user") User user);

在UserMapper.xml里面添加

    <insert id="insert" >
        insert into user
        value (
            #{user.id},
            #{user.userName},
            #{user.age},
            #{user.address}
        )
    </insert>

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zw4hAPSu-1669084188561)(E:\localhost\mybatis\mybatis.assets\image-20221108160716423.png)]

点击发送,即可新增成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBIuJL9t-1669084188563)(E:\localhost\mybatis\mybatis.assets\image-20221108160812280.png)]

因为设置了自动递增,则id序列会自动递增

开发知识,这里为什么实力类要用json格式发送?为什么新增的时候,controller层里面的参数要用@RequestBody注解?

例如一个场景,用户在页面上面填写了信息,要新增的时候,前端就会把对应信息的含义和信息转成一个json字符串,发送到后端,后端来接收,但是,后端要直接接收json字符串,是不能接收的,会引起报错,那么就得需要用到@RequestBody 注解,在参数加上,就可以来处理接受前端传过来的json字符串数据

修改

在UserController里面添加

	@PostMapping("update")
    public void update(@RequestBody User user){
    
    
        service.update(user);
    }

UserService

void update(User user);

UserserviceImpl

    @Override
    public void update(User user) {
    
    
        mapper.update(user);
    }

UserMapper

    void update(@Param("user")User user);

Usermapper.xml

<update id="update" >
        update user set
                        username = #{user.userName},
                        age = #{user.age},
                        address = #{user.address}
        where
            id = #{user.id}
    </update>

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ARb9SU0S-1669084188565)(E:\localhost\mybatis\mybatis.assets\image-20221108162229627.png)]

修改后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k23ckpHD-1669084188566)(E:\localhost\mybatis\mybatis.assets\image-20221108162258307.png)]

删除

UserController

    @PostMapping("delete")
    public void delete(Integer id){
    
    
        service.delete(id);
    }

UserService

    void delete(Integer id);

UserServiceImpl

  @Override
    public void delete(Integer id) {
    
    
         mapper.delete(id);
    }

UserMapper

void delete(@Param("id") Integer id);

UserMapper.xml

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

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PFr2CJo-1669084188568)(E:\localhost\mybatis\mybatis.assets\image-20221108164337171.png)]

控制台日志

==>  Preparing: delete from user where id = ?
==> Parameters: 20(Integer)
<==    Updates: 1

完成了删除

三、MyBatis获取参数值的两种方式(重点,面试常考)

${}和#{}

${}和#{} 区别

MyBatis获取参数值的两种方式:${}和#{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;

但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号

#{}是预编译)处理,是占位符,${}是字符串替换,是拼接符

Mybatis在处理#{}的时候会将sql中的#{}替换成?号,调用PreparedStatement来赋值

演示

//Controller
@GetMapping("findByUserName")
public User findByUserName(@RequestParam("userName") String userName){
    
    
    return service.findByUserName(userName);
}

//Service 
    User findByUserName(String userName);

//ServiceImpl
    @Override
    public User findByUserName(String userName) {
    
    
        return mapper.findByUserName(userName);
    }

//Mapper
    User findByUserName(@Param("userName") String userName);

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKT4ArLi-1669084188569)(E:\localhost\mybatis\mybatis.assets\image-20221108233433909.png)]

当使用#{}

UserMapper.xml

<select id="findByUserName" resultType="com.yhn.entity.User">
        select * from user  where username = #{userName}
    </select>

控制台日志输出:

==>  Preparing: select * from user where username = ?
==> Parameters: 武光职(String)
<==    Columns: id, username, age, address
<==        Row: 21, 武光职, 20, 湖北
<==      Total: 1

当使用${} 加 ‘’

    <select id="findByUserName" resultType="com.yhn.entity.User">
        select * from user  where username =  '${userName}'
    </select>

控制台输出

JDBC Connection [HikariProxyConnection@1631783826 wrapping com.mysql.cj.jdbc.ConnectionImpl@751e3ee7] will not be managed by Spring
==>  Preparing: select * from user where username = '武光职'
==> Parameters: 
<==    Columns: id, username, age, address
<==        Row: 21, 武光职, 20, 湖北
<==      Total: 1

当使用${} 不加 ‘’

   <select id="findByUserName" resultType="com.yhn.entity.User">
        select * from user  where username =  ${userName}
    </select>

控制台输出

threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column '武光职' in 'where clause'
### The error may exist in file [G:xxx/xxx/xxx]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select * from user  where username =  武光职
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '武光职' in 'where clause'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column '武光职' in 'where clause'] with root cause

java.sql.SQLSyntaxErrorException: Unknown column '武光职' in 'where clause'

意思就是SQL语法错误

Unknown column ‘武光职’ in ‘where clause’

意思是找不到

那么为什么会有这样的情况呢?

那是因为 #{} 会在自动在值两旁加上 ‘’ 而${}并不会

也就是跟前面提到的 :

#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号

而如果是 int类型呢?

在来测试 UserController 下的 findById 方法

   <!-- User findById(@Param("id") Integer id);  -->
	<select id="findById" resultType="com.yhn.entity.User">
        select * from user where id = ${id}
    </select>

控制台输出

==>  Preparing: select * from user where id = 1
==> Parameters: 
<==    Columns: id, username, age, address
<==        Row: 1, UZI, 19, 上海
<==      Total: 1

照样能输出结果 因为此时是个int类型的 在SQL语句中 int类型的数据 本身就不需要 ‘’ 而日期 字符串类型就需要,否则就会报错

${}和#{} 的使用技巧

性能考虑

因为预编译语句对象可以重复利用,把一个sql预编译后产生的PreparedStatement对象缓存下来,下次对于同一个sql,可以直接使用缓存的PreparedStatement对象,mybatis默认情况下,对所有的sql进行预编译,这样的话#{}的处理方式性能会相对高些。

安全考虑

如果作为条件变量的话,那么使用 #{} 更安全

性能不做案例,下面做安全的案例

这是一条用户的账号、密码数据

img

当用户登录,我们验证账号密码是否正确时用这个sql:

在这里插入图片描述

select * from user where username=${username} and password=${password}

显然这条sql没问题可以查出来,但是如果有人不知道密码但是想登录账号怎么办

我们不需要填写正确的密码:

username=yyy ; password=1 or 1=1,sql执行的其实是

select * from user where username='yyy' and password=1 or 1 =1

注意:这里的yyy外面的单引号不是 符 号 提 供 的 。 {}符号提供的。 {}没有这个功能,可以是sql手动拼接的,这里前后逻辑可能并不严密,但是sql入去最简单的例子就是这样。

所以#{} 更安全 因为他会自动添加单引号

select * from user where username=#{username} and password=#{password}

username=yyy ; password=1 or 1=1,sql执行的其实是

在这里插入图片描述

此时password密码直接错误,别人进不去

如何选择使用 #{}和${}呢?

表名、order by的排序字段作为变量时,使用${}。

能使用#{}的时候尽量使用#{}

我是程序员小孟,欢迎点赞关注!持续更新干货!

猜你喜欢

转载自blog.csdn.net/mengchuan6666/article/details/127977946