赞
踩
MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)作为数据库中的记录。通过使用MyBatis,开发人员可以方便地执行CRUD操作(创建、读取、更新和删除),以及复杂的数据库查询和存储过程的调用。
简单来说,MyBatis是一个简单、灵活和强大的持久层框架和数据库交互的工具,广泛应用于Java开发中,使得数据访问变得更加便捷和高效。
大概流程:前端发生Ajax请求给控制器,控制器调用服务层,服务层进行编排,服务层调用mybatis,mybatis调用数据库,再逐层返回。
MyBatis:在interface中声明方法,在xml中实现方法。mybatis是基于这两个实现的。
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在⾯向
对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象的互相转换:
将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL
将结果集映射为返回对象,即输出对象ORM 把数据库映射为对象:
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。
也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间
的转换。
打开MySQL客户端进行登录后,直接拷贝下面代码复制粘贴就行。
代码如下:
-- 创建数据库 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 timestamp default current_timestamp, updatetime timestamp default current_timestamp, `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 timestamp default current_timestamp, updatetime timestamp default current_timestamp, 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 timestamp default current_timestamp, updatetime timestamp default current_timestamp, 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);
在创建新Spring Boot项目时,添加依赖:
在SQL中添加MyBatis Framework
和 MySQL Driver
在旧项目中添加新依赖:
<!-- 添加 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>
application.yml
添加如下内容:
spring: datasource: url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 设置MyBatis mybatis: mapper-locations: - classpath:/mybatis/*Mapper.xml #打印MyBatis 执行SQL configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: com.example.demo: debug
注意:如果使用mysql-connector-java是5.x之前的,使用的是“ com.mysql.jdbc.Driver ” ;如果是⼤于 5.x,使⽤的是“ com.mysql.cj.jdbc.Driver ” 。
application.yml
添加如下内容:
# 设置MyBatis
#保存路径 /mybatis
#保存文件后缀 名 Mapper.xml
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml
某某Mapper.xml
。比如与用户有关的叫做UserMapper.xml
。MyBatis模式开发由两部分组成:
Interface
:其他层可以注入使用的接口。.xml
:实现Interface的方法,而SQL语句就在xml文件中。运行到MyBatis这块,Mybatis会生成一个代理对象,代理对象会将interface的方法声明和xml中的方法实现组合成代理对象的方法进行填充。实际上服务层调用MyBatis时调用的就是这个代理对象,代理对象就是一个普通类,普通类自然而然就有方法和方法实现。
1.新建一个mybatis文件夹:
2.添加实体类:
package com.example.demo.entity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @Data //自动添加set和get方法 public class UserInfoEntity { private Integer id; private String username; private String password; private String photo; //时间格式化 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private LocalDateTime createTime; private LocalDateTime updateTime; }
注意:属性名和数据库中的字段名保持一致,MyBatis会自动把类和数据库中的数据关联。
3.创建Mapper:
在demo下创建mapper包,再创建相应的Interface【添加@Mapper注解】
添加接口方法,用于查询:
package com.example.demo.mapper;
import com.example.demo.entity.UserInfoEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper //注意添加注解
public interface UserMapper {
List<UserInfoEntity> getAll();
}
<?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>
4.在mybati文件夹下创建一个
xxxMapper.xml
,此处创建UserMapper.xml
,用于查询用户操作。
添加sql语句:
<select id="getAll" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo
</select>
5.添加Service:
咱们说,服务器是调用的,所以使用Service调用:
package com.example.demo.service; import com.example.demo.entity.UserInfoEntity; import com.example.demo.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { @Autowired private UserMapper userMapper; public List<UserInfoEntity> getAll() { return userMapper.getAll(); } }
6.添加Controller:
package com.example.demo.controller; import com.example.demo.entity.UserInfoEntity; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/user") public class UserController { @Autowired UserService userService; @RequestMapping("/getAll") public List<UserInfoEntity> getAll(){ return userService.getAll(); } }
7.运行项目,输入url地址
⽤户的增加、删除和修改的操作,对应使⽤ MyBatis 的标签如下:
mapper 接口方法 实现代码:
int add(@Param("username") String username,@Param("password") String password);
xml的sql语句具体实现:
默认情况下返回的是受影响的⾏号。
<insert id="add">
insert into userinfo(username,password)
value(#{username},#{password})
</insert>
service代码:
public int add(@Param("username") String username, @Param("password") String password){
return userMapper.add(username,password);
}
controller代码:
@RequestMapping("/add")
public int add(@Param("username") String username, @Param("password") String password){
return userService.add(username,password);
}
重新运行项目,输入url,输入参数:
查询数据库,看是否添加成功:
因为除了controller里的代码和sql语句会有些不同,其余层代码基本一致【照着增加操作模仿】,所以以下只写controller代码和SQL语句:
Controller代码:
@RequestMapping("/update")
public int update(@Param("id")Integer id,@Param("username") String username){
return userService.update(id,username);
}
XML中的SQL语句:
<update id="update">
update userinfo set username=#{username} where id=#{id}
</update>
运行结果:
其他代码雷同,SQL语句:
<delete id="delById">
delete from userinfo where id=#{id}
</delete>
实现根据用户id查询用户信息的功能:
Controller代码如下:
@RequestMapping("/getuser")
public UserInfoEntity getUserById(Integer id) {
return userService.getUserById(id);
}
XML的SQL语句实现如下:
<select id="getUserById" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo where id=#{id}
</select>
在MyBatis中,#{}
和 ${}
是参数占位符,用于在SQL语句中插入参数值。它们的主要区别在于如何处理参数。
#{}
预编译处理:
MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement
的 set ⽅法来赋值。
使用 #{}
时,MyBatis会将参数值作为预编译的参数进行处理,可以有效防止SQL注入攻击。
#{}
会自动进行参数类型转换和安全处理,使得传递参数更加方便和安全。
#{}
可以直接在SQL语句中使用,例如 WHERE column_name = #{paramName}
。
${}
直接替换处理:
${}
不会自动添加 ''
号,而#{}
会自动加上''
号。${}
时,MyBatis会将参数值直接拼接到SQL语句中,类似于字符串替换。${}
不会对参数进行安全处理或类型转换,可能存在SQL注入风险。${}
可以用于动态生成SQL语句的部分内容,例如表名、列名等。综上所述,#{}
和 ${}
的区别主要在于参数的处理方式和安全性:
#{}
是推荐的使用方式,能够提供更好的安全性和可读性,并且支持参数类型转换。${}
可以用于一些特殊场景,例如动态生成SQL语句的部分内容,但需要注意潜在的SQL注入问题。在编写MyBatis的SQL语句时,请根据具体需求选择合适的参数占位符,以确保代码的安全和稳定性。
请注意,有些特殊情况下(例如在动态SQL中),可能需要结合使用 #{}
和 ${}
来实现更灵活的功能。
SQL注入是一种常见的安全漏洞,攻击者可以在应用程序中的输入点插入恶意的SQL代码来执行未经授权的操作或获取敏感数据。以下是一个简单的SQL注入示例:
假设有一个简单的登录功能,用户可以通过输入用户名和密码进行身份验证:
@RequestMapping("/islogin")
public UserInfoEntity isLogin(String username,String password){
return userService.isLogin(username,password);
}
XML代码:
<select id="isLogin" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo where username='${username}' and password='${password}'
</select>
在上述代码中,使用直接替换的方式将用户输入内容直接替换到SQL查询语句中。如果攻击者在用户名或密码输入框中输入恶意的值,就可以构造出恶意的SQL语句,例如:
localhost:8080/user/islogin?username=lisi&password='or 1=1 and id = '5
假设MySQL的userinfo表如下:
使用postman演示:
最终构成的代码是:
SELECT * FROM userinfo WHERE username = 'lisi' AND password = ''or 1=1 and id = '5'
这个恶意的SQL语句绕过了正常的身份验证逻辑,使得查询条件始终为真,因此可以绕过登录功能并返回所有用户的数据。要防止SQL注入攻击,可以使用参数化查询(使用#{}
参数占位符)或预编译语句来构建SQL语句。
简单使用#{} 进行like查询会无结果的:
<select id="findUserByName" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo where username like '%#{username}%'
</select>
在上述示例中,#{username}
是占位符,%
是通配符。如果传递username值为“lisi”相当于:select * from userinfo where username like '%'lisi'%';
是查询不到结果的。
进行like查询,需要使用#{},加上配合mysql内置函数 concat()
来实现:
<select id="findUserByName" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo where username like concat('%',#{usernam
e});
</select>
在上述示例中,#{username}
是占位符,可以通过参数传递具体的值。使用CONCAT()
函数将占位符和%
通配符连接在一起,实现以指定值开头的模糊匹配。
如果是增、删、改返回受影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的,因为默认是返回受影响的行数,如下图所示:
但是,查询操作不设置返回类型,则会报错:
<select id="getById" >
select * from userinfo where id=#{id}
</select>
运行结果:
显示运行了一个查询但没有找到结果映射,也就是说对于< select >
查询标签至少需要两个属性:
< resultMap>
与 < resultType>
大多数情况下都可以使用resultType
,原因是使用方便,直接定义到某个实体类的路径就行;
<select id="getAll" resultType="com.example.demo.entity.UserInfoEntity">
select * from userinfo
</select>
表示将查询结果映射到UserInfoEntity
对象。
resultMap使用场景:
<resultMap id="userResultMap" type="com.example.User">
<id property="id" column="user_id"></id>
<result property="username" column="user_name"><]</result>
<result property="email" column="user_email"></result>>
<!-- 其他映射关系 -->
<!-- ..... -->
</resultMap>
<select id="getAll" resultMap="userResultMap">
select * from userinfo
</select>
在上述示例中,<resultMap>
定义了User
类与查询结果的映射关系。具体解释如下:
property
属性:property
属性指定Java对象属性的名称。column
属性:column
属性指定数据库表中对应字段的列名。id
属性:主键通过配置合适的<resultMap>
,可以灵活地定义不同查询的映射关系,以满足特定业务需求。
使用时,只需要将ResultMap配置的id名,添加到相应的位置,如上述代码中< select >
标签
java类属性如下:
数据库字段如下:
虽然属性和字段名不一致,但通过上述resultMap配置后,一样可以成功映射:
ArticleInfo类:
package com.example.demo.entity; import lombok.Data; import java.time.LocalDateTime; @Data public class ArticleInfo { private int id; private String title; private String content; private LocalDateTime createtime; private LocalDateTime updatatime; private int uid; @Override public String toString() { return "ArticleInfo{" + "id=" + id + ", title='" + title + '\'' + ", content='" + content + '\'' + ", createtime=" + createtime + ", updatatime=" + updatatime + ", uid=" + uid + '}'; } }
ArticleInfoVO类:
package com.example.demo.entity.vo; import com.example.demo.entity.ArticleInfo; import lombok.Data; @Data public class ArticleInfoVO extends ArticleInfo { private String username; @Override public String toString() { return "ArticleInfoVO{" + "username='" + username + '\'' + "} " + super.toString(); } }
ArticleMapper:
package com.example.demo.mapper;
import com.example.demo.entity.vo.ArticleInfoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleMapper {
//查询文章详情
ArticleInfoVO getDetail(@Param("id")Integer id);
}
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.ArticleMapper">
<select id="getDetail" 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>
</mapper>
Test测试类:
这是一个单元测试的类:
package com.example.demo.mapper; import com.example.demo.entity.vo.ArticleInfoVO; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class ArticleMapperTest { @Autowired private ArticleMapper articleMapper; @Test void getDetail() { ArticleInfoVO articleInfoVO = articleMapper.getDetail(2); System.out.println(articleInfoVO); } }
测试类运行结果:
MyBatis提供了强大的动态SQL功能,可以根据不同的条件来动态生成SQL语句。大白话就是允许我们在xml进行条件判断。
【mybatis】
假设在注册一个账号时,需要输入账户和密码【必填字段】,而性别可以选择性填写【非必填字段】,那么如果有不确定的字段传入,这个时候就需要使用动态标签< if >
来判断了,比如性别sex可为非必填字段,具体实现如下:
<insert id="add">
insert into userinfo
value(#{username},#{password},
<if test="sex != null">
sex = #{sex},
</if>
#{age}
)
</insert>
在上述示例中,<if>
标签被嵌套在<inser>
语句内部。<if>
标签中的test
属性指定了条件表达式,test
中传入的是对象属性,满足条件时才会包含对应的SQL代码。
在示例中,如果传入的userinfo
对象的sex
属性不为空,则生成一个sex = #{sex},
的条件;这样就可以根据不同情况拼接不同的查询条件。
<if>
标签还支持更复杂的条件判断,可以使用比较运算符、逻辑运算符、调用对象方法等。
需要注意的是,在使用<if>
时需要遵循以下几点:
null
时,要使用" != null"进行判断,不能直接使用"=="。<if test="name != null and name != ''">
。<trim>
标签是用于处理SQL语句中多余的空格和逗号的XML元素。它可以根据需要添加或移除SQL语句中的前缀、后缀、前后缀或者连接词。
<trim>
标签有以下属性可用:
prefix
:在SQL语句开头添加的前缀。prefixOverrides
:需要移除的前缀内容。suffix
:在SQL语句末尾添加的后缀。suffixOverrides
:需要移除的后缀内容。下面是一个使用<trim>
标签的示例:
<select id="getUserList" parameterType="com.example.User" resultType="com.example.User">
SELECT * FROM users
WHERE
id =#{id} and
<trim prefix="AND" prefixOverrides="OR">
<if test="name != null and name != ''">
OR name = #{name}
</if>
<if test="age != null">
OR age = #{age}
</if>
</trim>
</select>
在上述示例中,<trim>
标签被嵌套在<where>
语句内部。<trim>
标签根据指定的条件添加或移除相应的前缀和前缀内容。
在示例中,如果传入的User
对象的name
属性不为空,则生成一个前缀为"AND"的条件,并移除前缀中多余的"OR",也就是AND name = #{name}
;如果age
属性不为空,则生成一个前缀为"AND"的条件,并移除前缀中多余的"OR",也就是AND age = #{age}
。这样可以动态地拼接多个条件。当然也可以拼,
号;(
号、)
号。
比如:
<insert id="add"> insert into user <trim prefix="(" suffix=")" suffixOverrides=","> <if test="username != null"> username, </if> <if test="password != null"> password, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="username != null"> #{username}, </if> <if test="password != null"> #{password}, </if> </trim> </insert>
上述代码:在第一个< trim>
标签,在语句开始部分会加上(
,如果传入的username
不为空,则会生成一个username
并把,
去掉,如果传入的password
不为空,则会生成一个password
并把,
去掉,最后加上)
;
在第二个< trim>
标签,在语句开始部分会加上values (
,如果传入的username
不为空,则会生成一个username
并把,
去掉,如果传入的password
不为空,则会生成一个password
并把,
去掉,最后加上)
;
<where>
标签是用于动态生成SQL语句中的WHERE
。它可以根据条件判断自动生成和连接各个查询条件,并且能够处理多余的逻辑运算符(如AND
、OR
)。
下面是一个使用<where>
标签的示例:
<select id="getUserList" parameterType="com.example.User" resultType="com.example.User">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
在上述示例中,<where>
标签包含了两个<if>
标签,每个<if>
标签表示一个查询条件。当条件满足时,该条件会被添加到WHERE
子句中。
如果传入的User
对象的name
属性不为空,则生成一个条件 name = #{name}
,会自动把多余的AND
去掉;如果age
属性不为空,则生成一个条件AND age = #{age}
,此处的AND
是有效的,就不会去掉了。
使用<where>
标签能够动态生成合理的WHERE
子句,避免因为没有查询条件而导致的无效或错误的SQL语句。
需要注意的是,<where>
标签会自动处理多余的逻辑运算符,例如只有一个条件时,生成的SQL语句不会包含前置的AND
。
<set>
标签是用于动态生成UPDATE
语句中SET
子句的XML元素。它可以根据条件判断自动生成要更新的字段和对应的值,并且能够处理多余的逗号(,
)。
下面是一个使用<set>
标签的示例:
<update id="updateUser" parameterType="com.example.User">
UPDATE users
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>
在上述示例中,<set>
标签包含了两个<if>
标签,每个<if>
标签表示一个要更新的字段和对应的值。再次强调test
传入的就是对象属性;当条件满足时,该字段和值会被添加到SET子句中。
如果传入的User
对象的name
属性不为空,则生成一条更新语句name = #{name},
;如果age
属性不为空,则生成一条更新语句age = #{age},
,并会自动的去掉最后一个逗号(,
)。
使用<set>
标签能够动态生成合理的SET子句,避免因为没有更新字段而导致的无效或错误的SQL语句。同时,<set>
标签会自动处理多余的逗号,确保生成的UPDATE语句的语法正确。
需要注意的是,在编写UPDATE语句时,<set>
标签应该位于WHERE子句之前。
<foreach>
标签是用于在SQL语句中循环遍历集合或数组的XML元素。它可以方便地将集合或数组中的元素插入到SQL语句中,生成批量操作的语句。
对集合进⾏遍历时可以使⽤该标签。< foreach >标签有如下属性:
collection:指定要遍历的集合或数组,如 List,Set,Map或数组对象
item:指定在每次迭代中将集合或数组的元素赋值给的变量名。
index:指定在每次循环迭代中,集合中的当前索引将被赋值给的变量。
open:语句块开头的字符串
close:语句块结束的字符串
separator:指定每个循环迭代之间的分隔符。
下面是一个使用<foreach>
标签的示例:
<insert id="addList">
INSERT INTO users (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
在上述示例中,<foreach>
标签是在INSERT语句中遍历List
类型的参数list
。每次迭代都将取出集合中的元素赋值给user
变量,然后将该元素的属性name
和age
插入到VALUES子句中。
通过使用<foreach>
标签,可以轻松实现对集合或数组进行批量操作,如批量插入、批量更新等。
需要注意的是,<foreach>
标签也支持一些其他属性,例如index
属性表示当前迭代的索引位置,open
和close
属性用于定义在循环的开头和结尾添加的字符串。
以下是一个使用open
和close
属性的示例:
<select id="getUsersByIdList" parameterType="java.util.List" resultType="com.example.User">
SELECT * FROM users
WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
open
属性定义了循环的开头字符串,它会在整个循环的最前面被插入。一般情况下,我们可以将它用来添加开始的括号、引号等。如果不需要在开头添加任何字符串,可以将open
属性设置为空字符串或省略不写。
在上述示例中,我们想要查询一组用户的信息,这组用户的ID存储在一个List
类型的参数 list
中。通过使用<foreach>
标签,我们将每个ID作为查询条件的一部分,将它们以逗号分隔放在括号内。
假设list
中包含了三个ID,那么生成的SQL语句将类似于:
SELECT * FROM users WHERE id IN (1, 2, 3);
通过在<foreach>
标签中使用open
和close
属性,我们可以方便地添加括号使查询条件得到正确的语法结构。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。