在前面,我们学习过JDBC编程对数据库的操作,不过JDBC对于数据库的操作在步骤上过于麻烦,所以我们在Spring中引入了MyBatis.
MyBatis操作数据库的步骤:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
如果我们在创建工程的时候忘记勾选了这两个选项,也可以通过右键点击生成,选择edit starter自动创建依赖.
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
`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(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加用户信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
package com.jrj.mybatis;
import lombok.Data;
import java.util.Date;
@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;
}
MyBatis中要连接数据库需要数据库相关参数的配置,包括数据库URL,用户名,密码,MySQL驱动类.这些配置信息均在配置文件中完成.用于连接对应的数据库.下面我们以yml文件为例:
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
其中,mybatis_test就对应的是数据库的名字.
[注意] 如果password是纯数字的话,需要在数字的的外围用单引号括起来.
我们可以通过在测试类中编写代码来测试与数据库的连接是否成功.
@SpringBootTest
class ForumsApplicationTests {
@Autowired
public DataSource dataSource;
@Test
void testDataSource() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
package com.jrj.mybatis.mapper;
import com.jrj.mybatis.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo")
public List<UserInfo> selectAllUser();
}
@Mapper注解: 表示的是MyBatis中的Mapper接口,在程序运行的时候,这个接口会交给IoC容器管理.
@Select注解: 表示的是sql语句是一个select查询语句,也就是注解方法具体实现的内容.
需要注意的一点是,像这样返回多条数据的接口,一定要用List来接收.
如何生成测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAllUser() {
List<UserInfo> list = userInfoMapper.selectAllUser();
for (UserInfo userInfo:list){
System.out.println(userInfo);
}
}
}
@SpringBootTest注解:会在测试类运行的时候,自动加载Spring环境,@Autowired进行DI注入之后,我们便可以使用对应的类调用对应的方法进行测试.@Test注解表示该方法是测试方法, 加上该注解后会在运行测试方法的时候在控制台上显示测试方法的运行结果.在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果,在配置文件中进行配置即可.下面是yml配置文件的格式:
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运行程序,我们可以看到sql语句的内容,以及传递的参数类型和执行结果.
现在我们需要查找id=4的用户,对应的sql是select * from userinfo where id=4.
@Select("select * form userinfo where id = 4")
public UserInfo selectUser();
但是这样的话,就只能查询到id为4用户的数据,所以我们不建议把sql语句这样写死.需要变为动态的数值.
解决办法就是在selectUser方法中添加一个参数(id),将方法中的参数传递给sql语句.使用#{}的方式在sql语句中获取方法中的参数.
@Select("select * from userinfo where id = #{id}")
public UserInfo selectUser(Integer id);
void selectUser() {
System.out.println(userInfoMapper.selectUser(4));
}
运行测试代码:
如果mapper接口方法中的形式参数只有一个,#{}里面的属性名可以随便写.因为参数和属性名都只有一个,只能是一一对应的关系.如果参数有多个,属性名和参数名就必须保持一致,或者使用param1,param2…按照顺序对应.
@Select("select * from userinfo where username = #{name} and id = #{id}")
public UserInfo selectUser2(String name,Integer id);
@Test
void selectUser2() {
System.out.println(userInfoMapper.selectUser2("admin",1));
}
测试结果:
@Select("select * from userinfo where username = #{param1} and id = #{param2}")
public UserInfo selectUser3(String name,Integer id);
@Test
void selectUser3() {
System.out.println(userInfoMapper.selectUser3("admin",1));
}
测试结果:
也可以通过@Param注解,设置参数的别名,如果使用@Param设置别名,注解中的别名必须和sql中的属性名保持一致.
@Select("select * from userinfo where id = #{ID}")
public UserInfo selectUser4(@Param("ID") Integer id);
@Test
void selectUser4() {
System.out.println(userInfoMapper.selectUser4(1));
}
运行结果:
sql语句:
insert into userinfo (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")
把sql中的常量换为动态的参数.
Mapper接口:
@Insert("insert into userinfo (id,username,password,age,gender,phone) values (" +
"#{id},#{username},#{password},#{age},#{gender},#{phone})")
public Integer insertUser1(UserInfo userInfo);
这里我们可以直接使用UserInfo对象的属性来获取参数.由于Insert返回的是改变数据库的行数,所以接口的返回值是Integer.
@Test
void insertUser1() {
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setUsername("wangwu");
userInfo.setPassword("12345");
userInfo.setAge(20);
userInfo.setGender(1);
userInfo.setPhone("133347607");
userInfoMapper.insertUser1(userInfo);
}
运行结果:
查看数据库:id为5的数据插入成功.
接下来我们来设置@Param属性,那么#{}就需要用参数.属性的方式来获取.
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into userinfo (id,username,password,age,gender,phone) values (" +
"#{id},#{username},#{password},#{age},#{gender},#{phone})")
public Integer insertUser2(UserInfo userInfo);
其中useGeneratedKeys表示的是是自增?keyProperty表示的是哪个字段自增?
@Test
void insertUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("1234566");
userInfo.setAge(21);
userInfo.setGender(0);
userInfo.setPhone("13334098738");
System.out.println("change col " + userInfoMapper.insertUser2(userInfo) + "id = " + userInfo.getId());
}
测试运行:
我们看到影响的行数是1,自增的id为7.下面我们观察数据库:
sql语句:
delete from userinfo where id=6.
把sql语句中的常量替换为动态参数.
@Delete("delete from userinfo where id = #{id}")
public Integer deleteUser1(Integer id);
@Test
void deleteUser1() {
System.out.println(userInfoMapper.deleteUser1(6));
}
查看数据库,我们看到id=6的数据被删除掉了.
sql语句:
update userinfo set username="zhaoliu" where id=5
把SQL中的常量替换为动态的参数
Mapper接口:
@Update("update userinfo set username = #{username} where id = #{id}")
public Integer updateUser(UserInfo userInfo);
@Test
void updateUser() {
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setUsername("tianqi");
System.out.println(userInfoMapper.updateUser(userInfo));
}
测试运行:我们看到数据库中id为5的名字被改成了tianqi.
我们在上面查询的时候,发现有几个字段是没有赋值的.只有Java对象属性和数据库字段⼀模⼀样时,才会进行赋值.
在我们查询所有数据的时候,我们发现,对象的创建时间,更新时间,删除逻辑数字都是null.
原因:
当自动映射查询结果的时候,MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性(忽略大小写) .这意味着如果发现了ID列和id属性,MyBatis会将列ID的值赋给id属性.但是我们的创建时间,更新时间,删除逻辑数字,在数据库中是蛇形结构的名字,而在Java类中是小驼峰的格式.
下面是解决办法:
在sql语句中,给列名起别名,保持别名和实体类属性名一样.
@Select("select id, username, `password`, age, gender, phone, " +
"delete_flag as deleteFlag," +
"create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> selectAllUser2();
当sql语句太长的时候,我们可以使用+进行拼接.
@Results({
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("select * from userinfo")
public List<UserInfo> selectAllUser3();
@Results注解中可以用大括号括起多个@Result映射,@Result前面的参数是表的字段名,后面是Java类的属性.也就是字段与属性的映射关系.
@Test
void selectAllUser3() {
List<UserInfo> list = userInfoMapper.selectAllUser3();
for (UserInfo userInfo:list){
System.out.println(userInfo);
}
}
运行测试:
我们看到,后面的几个属性被显示了出来.
如果其他sql也想复用这一组映射,可以给这一组@Results映射自定义一个名称. 之后在想要复用这个sql映射的地方使用@ResultMap(映射名称)来复用映射.
@Results(id = "resultMap1",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("select * from userinfo")
public List<UserInfo> selectAllUser3();
@ResultMap(value = "resultMap1")
@Select("select * from userinfo where id = #{id}")
public UserInfo selectUser5(Integer id);
@ResultMap注解中的value的值和上面映射的id名字一样.方可复用.
我们也可以在xml文件中使用<resultMap>和<result>标签来完成.
<resultMap id="BaseResultMap" type="com.jrj.forums.model.Article">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="board_id" jdbcType="BIGINT" property="boardId" />
<result column="user_id" jdbcType="BIGINT" property="userId" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="visit_count" jdbcType="INTEGER" property="visitCount" />
<result column="reply_count" jdbcType="INTEGER" property="replyCount" />
<result column="like_count" jdbcType="INTEGER" property="likeCount" />
<result column="state" jdbcType="TINYINT" property="state" />
<result column="delete_state" jdbcType="TINYINT" property="deleteState" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>
指定好结果映射之后,就可以在对应的sql标签之后使用resultMap来指定结果映射
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
...
</select>
我们也可以使用extends来继承结果映射,之后再继承过来的结果映射中添加还未存在的字段.
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.jrj.forums.model.Article">
<result column="content" jdbcType="LONGVARCHAR" property="content" />
</resultMap>
我们还可以在<resultMap>中使用<association>标签对类中的属性进行映射,<association>中查询到的结果会映射到类中的属性中.
比如查询出来的resultMap="com.jrj.forums.dao.UserMapper.BaseResultMap"结果会映射到Article类中的user属性中.
<resultMap id="AllInfoResultMap" type="com.jrj.forums.model.Article" extends="ResultMapWithBLOBs">
<!-- 在查询的文章结果中添加用户结果,我们使用association标签来扩展-->
<association property="user" resultMap="com.jrj.forums.dao.UserMapper.BaseResultMap" columnPrefix="u_"/>
<association property="board" resultMap="com.jrj.forums.dao.BoardMapper.BaseResultMap" columnPrefix="b_"/>
</resultMap>
其中,columnPrefix代表的是查询的时候使用的前缀,只要在查询的时候,字段名加上指定的前缀,把字段映射到指定的属性上.
比如:u.id as u_id这个字段就会映射到user属性中去.
通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java属性⼀般遵循驼峰命名法约定.
为了在这两种命名方式之间启用自动映射,需要将mapUnderscoreToCamelCase设置为true.我们需要在配置文件中配置.
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
转换规则:abc_xyz=>abcXyz蛇形转换为小驼峰格式.
Java代码不做任何处理:
@Select("select * from userinfo")
public List<UserInfo> selectAllUser();
我们看到原来没有赋值上的属性被赋值了.
mysql表的设计规范:
自增id,创建日期,更新日期,这几个字段即使业务没有需求,也必须有.
当我们在数据库中需要存储一些对称加密的数据的时候,我们就需要使用MyBatis中的数据类型自动转换的功能,比如我们需要在数据库中存储用户的手机号码,用户的手机号码属于用户的隐私数据,我们就需要单独创建一个Encrypt类,代表的是需要对称加密的类,类中包含一个value属性,代表的是需要被加密解密的字符串,手机号就需要赋值给value这个属性,
@Data
//需要加密的类
public class Encrypt {
private String value;//需要被加密的字符串
public Encrypt(){}
public Encrypt(String value){
this.value = value;
}
}
在我们向数据库中存储数据的时候,数据是Encrypt类型,在存储进数据库之后,数据就变成了数据库中所指定的类型,在从数据库中取出的时候,就变成了Encrypt类型.
之后我们就来编写转换器(handler),在我们编写转换器的时候,需要使用到@MappedTypes()后面指定的是需要被转换的java类型的类对象,还需要使用到@MappedJdbcTypes()后面指定的数据库中的数据类型的类对象.
/**
* MyBatis取出和放入数据时对数据进行自动加密和解密
*/
@MappedTypes(Encrypt.class)//原本的数据类型
@MappedJdbcTypes(JdbcType.VARCHAR)//sql中的数据类型
public class EncryptTypeHandler extends BaseTypeHandler<Encrypt> {
//对手机号使用对称加密
private static byte[] KEY = "1234567abcdefg".getBytes(StandardCharsets.UTF_8);
/**
* 参数设置(加密并设置对象)
* @param ps sql预编译对象
* @param i 需要赋值的索引位置
* @param parameter 原本位置i需要赋的值
* @param jdbcType JDBC类型数据
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException {
//添加的数据为空
if (parameter == null || parameter.getValue() == null){
ps.setString(i,null);//由于数据库中对应的是varchar数据类型,所以这里使用的是setString方法,即对应java中的String类型
return;
}
//对数据进行加密之后放入数据库中
AES aes = SecureUtil.aes(KEY);//设置密钥
String encrypted = aes.encryptHex(parameter.getValue());//对数据进行加密
ps.setString(i,encrypted);//把数据放入数据库的预编译对象中
}
/**
*
* @param rs 结果集
* @param columnName 索引名
* @return
* @throws SQLException
*/
@Override
public Encrypt getNullableResult(ResultSet rs, String columnName) throws SQLException {
return decrypt(rs.getString(columnName));//从指定的索引中获取加密对象之后解密
}
/**
*
* @param rs 结果集
* @param columnIndex 索引名
* @return
* @throws SQLException
*/
@Override
public Encrypt getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return decrypt(rs.getString(columnIndex));
}
/**
*
* @param cs 结果集
* @param columnIndex 索引名
* @return
* @throws SQLException
*/
@Override
public Encrypt getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return decrypt(cs.getString(columnIndex));
}
/**
* 解密
* @return 返回解密后的对象
*/
private Encrypt decrypt(String value){
if (value == null){
return null;
}
return new Encrypt(SecureUtil.aes(KEY).decryptStr(value));
}
}
我们需要在创建的转换器类型中继承BaseTypeHandler<>类型,泛型中指定的是需要被转换的java类型,之后我们在类中需要重写BaseTypeHandler中的方法,分别是以上所示的方法.
ps.setString(i,encrypted);代表的就是把指定的encrypted加密后的字符串使用setString方法设置到sql预编译对象ps中索引为i的位置.decrypt(rs.getString(columnIndex))代表的就是从结果集rs中使用getString方法从columnIndex索引位置取出指定的字符串之后并解密.MyBatis的开发方式从大方向上分为两种,除了我们上面展示的使用注解的方式,我们还可以通过xml配置文件来开发.
使⽤Mybatis的注解方式,主要是来完成⼀些简单的增删改查功能.如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中.
这种开发方式大致分为两步:
此步骤需要进行两项设置,数据库连接字符串设置和指定MyBatis的XML文件.
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
持久层代码分为两部分:
package com.jrj.mybatis.mapper;
import com.jrj.mybatis.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMLMapper {
public List<UserInfo> selectAllUser1();
}
<?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.jrj.mybatis.mapper.UserInfoXMLMapper">
</mapper>
mapper标签中加的是带有@Mapper注解接口的路径,即想要通过MyBatis操作数据库的Mapper接口.
查询所有用户的具体实现:
<?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.jrj.mybatis.mapper.UserInfoXMLMapper">
<select id="selectAllUser1" resultType="com.jrj.mybatis.UserInfo">
select * from userinfo
</select>
</mapper>
其中select标签中,id="selectAllUser1"代表的是Mapper中的方法名. resultType="com.jrj.mybatis.UserInfo"代表的是sql查询之后返回的类型,也就是我们开头定义的实体类,只有select类型的语句会有返回值的类型.注意,是sql查询之后返回的类型,不是接口返回值的类型,sql查询之后返回的是UserInfo类,而接口返回的是List类型.
标签中间写的是sql语句.
我们可以安装一个插件,叫做MyBatisX,这个插件可以自动帮助我们生成xml标签.我们是需要写sql语句即可.
注意,在我们的方法名中如果有select,Insert这样的关键字的时候,插件会自动根据名字自动生成标签类型.如果没有,需要在生成的时候进行选择.
@Test
void selectAllUser1() {
List<UserInfo> list = userInfoXMLMapper.selectAllUser1();
for (UserInfo userInfo: list){
System.out.println(userInfo);
}
}
测试结果:
当然,所谓的单元测试不一定只对Mapper中的方法进行测试,也可以对Service中,或者是Controller中的方法进行测试.
UserInfoMapper接口:
public Integer insertUser(UserInfo userInfo);
xml:
<insert id="insertUser">
insert into userinfo (id,username,password,age,gender,phone) values (#{id},#{username},#{password},#{age},#{gender},#{phone})
</insert>
测试代码:
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setId(8);
userInfo.setUsername("zhubajie");
userInfo.setAge(22);
userInfo.setPassword("6666666");
userInfo.setGender(0);
userInfo.setPhone("487362849326");
userInfoXMLMapper.insertUser(userInfo);
}
运行结果:
与注解实现类似,要是在形参的前面加上@Param注解的话,在sql语句中的#{}就必须使用对象名.属性名来访问.
Mapper接口:
public Integer insertUser(@Param("userinfo") UserInfo userInfo);
xml:
<insert id="insertUser">
insert into userinfo (id,username,password,age,gender,phone) values (#{userinfo.id},#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})
</insert>
删和改和上面的增是相同的道理,这里我们不再赘述.
同样,使用xml进行查询的时候,同样也存在sql字段名和类属性对应不上的问题.
xml:
<select id="selectAllUser1" resultType="com.jrj.mybatis.UserInfo">
select * from userinfo
</select>
Mapper接口:
public List<UserInfo> selectAllUser1();
运行结果:
我们看到一些字段名仍然没有被赋值.
解决办法与注解类似:
<resultMap id="Map" type="com.jrj.mybatis.UserInfo">
<id column="id" property="id"></id>
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update" property="updateTime"></result>
</resultMap>
<select id="selectAllUser1" resultType="com.jrj.mybatis.UserInfo" resultMap="Map">
select * from userinfo
</select>
解释:
多表查询和单表查询类似,只不过就是sql不同而已,具体sql可参考:
https://lilesily12385.blog.csdn.net/article/details/137519405
上面建了⼀张用户表,我们再来建⼀张文章表,进行多表关联查询.
文章表的uid,对应用户表的id.
-- 创建⽂章表
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
);
package com.jrj.mybatis;
import lombok.Data;
import java.util.Date;
@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;
}
需求,根据uid查询作者的名称等相关信息.
sql:
SELECT
ta.id,
ta.title,
ta.content,
ta.uid,
tb.username,
tb.age,
tb.gender
FROM
articleinfo ta
LEFT JOIN userinfo tb ON ta.uid = tb.id
WHERE
ta.id =1
补充实体类:
@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;
}
接口定义:
@Mapper
public interface ArticleInfoMapper {
@Select("select " +
"articleinfo.id,articleinfo.title,articleinfo.content,articleinfo.uid,articleinfo.delete_flag,articleinfo.create_time,articleinfo.update_time,userinfo.username,userinfo.age,userinfo.gender " +
"from " +
"articleinfo left join userinfo " +
"on articleinfo.uid = userinfo.id " +
"where userinfo.id = #{id}")
public ArticleInfo selectArticle(Integer id);
}
测试代码:
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
public ArticleInfoMapper articleInfoMapper;
@Test
void selectArticle() {
System.out.println(articleInfoMapper.selectArticle(1));
}
}
运行结果:
一般情况下,我们不会轻易使用联合查询,因为它是一种慢查询,会影响集群的性能.我们一般会把联合查询的sql拆分成多个单表查询.
当然有些情况下可以使用多表查询,这些情况对于性能要求都比较小:比如内部员工使用的系统,B端项目(平台的商家端).
在MyBatis中,参数赋值的方法不仅仅只有#{},而且还有${},下面我们来看一下二者的区别.
@Select("select * from userinfo where id = #{id}")
public UserInfo selectUser(Integer id);
观察日志:
我们发现,通过次接口输出的sql语句是:
select * form userinfo where id = ?
我们输⼊的参数并没有在后面拼接,id的值是使用? 进行占位.这种SQL我们称之为"预编译SQL".预编译sql我们在mysql中的JDBC编程中有提到过.就是把参数的值直接赋给了前面的id.
把#{}替换为${}之后再次观察打印日志:
@Select("select * from userinfo where id = ${id}")
public UserInfo selectUser(Integer id);
我们看到,参数直接拼接到了sql语句中.
2. 接下来就是String类型的参数
@Select("select * from userinfo where username = #{name}")
public UserInfo selectUser6(String name);
观察打印日志:
把#{}换为${}再观察打印日志.
@Select("select * from userinfo where username = ${name}")
public UserInfo selectUser6(String name);
观察日志:
我们发现,这次的结果依然是把参数直接拼接在了sql中,但是字符串作为参数的时候需要添加引号 ‘’ ,使
⽤ ${} 不会拼接引号 ‘’ ,导致程序报错.
我们修改代码:
@Select("select * from userinfo where username = '${name}'")
public UserInfo selectUser6(String name);
再次运行,正常返回:
从上面两个例子可以总结:
#{} 使用的是预编译SQL,通过?占位的方式,提前对SQL进行编译,然后把参数赋值到SQL语句中.#{} 会根据参数类型,自动拼接引号’’ .${} 会直接进行字符替换,直接把参数拼接到sql中,⼀起对SQL进行编译.如果参数为字符串,需要加上引号 ‘’ .
#{}和${}的区别就是预编译SQL和即时SQL的区别.
#{}相对于${}性能更高.#{}更安全(防止sql注入)@Select("select * from userinfo where username = '${name}'")
public UserInfo selectUser6(String name);
sql注入后:
@Test
void selectUser6() {
System.out.println(userInfoMapper.selectUser6("' or 1='1"));
}
运行结果:
sql就返回所有用户的信息.并不是我们想要的信息.所以我们尽量使用#{}的方式.
即使${}会有sql注入的风险.有些情况下,${}还是有他自己的作用.比如对数据进行排序.
Mapper实现:把数据根据id降序排名.
@Select("select * from userinfo order by id ${sort}")
public List<UserInfo> selectUser7(String sort);
@Test
void selectUser7() {
System.out.println(userInfoMapper.selectUser7("desc"));
}
返回了正确的结果;
==如果把KaTeX parse error: Expected 'EOF', got '#' at position 3: 改成#̲,就会自动对字符串加上``' …{}``.
like使用#{}会报错.会给字符串加上单引号.
@Select("select * from userinfo where username like '%#{name}%'")
public UserInfo selectUser8(String name);
@Test
void selectUser8() {
System.out.println(userInfoMapper.selectUser8("dmi"));
}
虽然使用${}会成功,但是存在sql注入问题,不可以直接使用.所以我们通过sql中的一个函数concat()来处理,它是一个字符串拼接函数.
@Select("select * from userinfo where username like concat('%',#{name},'%')")
public UserInfo selectUser8(String name);
运行成功:
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- baijiahaobaidu.com 版权所有 湘ICP备2023023988号-9
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务