一天快速掌握Mybaits[一]

Posted 程序员springmeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一天快速掌握Mybaits[一]相关的知识,希望对你有一定的参考价值。

一、搭环境

Spring Initializr的搭建

创建完毕后的项目结构

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

数据库环境的搭建

新建数据库

执行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语句


项目结构

二、基本的CRUD

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

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

结构搭建

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 插件

装上插件后的效果,蓝红鸟以线为例,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 接口没有任何参数 所有请求区为空

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

新增

在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>

测试

点击发送,即可新增成功

因为设置了自动递增,则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>

测试

修改后

删除

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>

测试

控制台日志

==>  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);

测试

当使用#

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进行预编译,这样的话#的处理方式性能会相对高些。

安全考虑

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

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

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

当用户登录,我们验证账号密码是否正确时用这个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的排序字段作为变量时,使用$。

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

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

以上是关于一天快速掌握Mybaits[一]的主要内容,如果未能解决你的问题,请参考以下文章

一天快速掌握Mybaits[一]

SSM springmvc mybaits websocket 服务器框架

springboot配置快速操作版(拦截器,事务日志mybaits)

SpringBoot一天快速入门,超级肝货!

SpringBoot一天快速入门,超级肝货!

Mybaits 快速入门代码实例(maven代码版)