发布时间:2025-06-24 18:46:57 作者:北方职教升学中心 阅读量:045
执行传递的参数以及执行结果,在配置文件中进行配置即可.下面是yml配置文件的格式:
mybatis:configuration:# 配置打印 MyBatis⽇志 log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
重新运行程序,我们可以看到sql语句的内容,以及传递的参数类型和执行结果.
2.2 参数传递
现在我们需要查找id=4的用户,对应的sql是select * from userinfo where id=4.
@Select("select * form userinfo where id = 4")publicUserInfoselectUser();
但是这样的话,就只能查询到id为4用户的数据,所以我们不建议把sql语句这样写死.需要变为动态的数值.
解决办法就是在selectUser方法中添加一个参数(id),将方法中的参数传递给sql语句.使用#{}
的方式在sql语句中获取方法中的参数.
@Select("select * from userinfo where id = #{id}")publicUserInfoselectUser(Integerid);
voidselectUser(){System.out.println(userInfoMapper.selectUser(4));}
运行测试代码:
如果mapper接口方法中的形式参数只有一个,#{}
里面的属性名可以随便写.因为参数和属性名都只有一个,只能是一一对应的关系.如果参数有多个,属性名和参数名就必须保持一致,或者使用param1,param2…按照顺序对应.
- 按照名字对应
@Select("select * from userinfo where username = #{name} and id = #{id}")publicUserInfoselectUser2(Stringname,Integerid);
@TestvoidselectUser2(){System.out.println(userInfoMapper.selectUser2("admin",1));}
测试结果:
- 按照参数顺序对应
@Select("select * from userinfo where username = #{param1} and id = #{param2}")publicUserInfoselectUser3(Stringname,Integerid);
@TestvoidselectUser3(){System.out.println(userInfoMapper.selectUser3("admin",1));}
测试结果:
也可以通过@Param
注解,设置参数的别名,如果使用@Param
设置别名,注解中的别名必须和sql中的属性名保持一致.
@Select("select * from userinfo where id = #{ID}")publicUserInfoselectUser4(@Param("ID")Integerid);
@TestvoidselectUser4(){System.out.println(userInfoMapper.selectUser4(1));}
运行结果:
2.3 增(insert)
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})")publicIntegerinsertUser1(UserInfouserInfo);
这里我们可以直接使用UserInfo对象的属性来获取参数.由于Insert返回的是改变数据库的行数,所以接口的返回值是Integer.
@TestvoidinsertUser1(){UserInfouserInfo =newUserInfo();userInfo.setId(5);userInfo.setUsername("wangwu");userInfo.setPassword("12345");userInfo.setAge(20);userInfo.setGender(1);userInfo.setPhone("13334768907");userInfoMapper.insertUser1(userInfo);}
运行结果:
查看数据库:id为5的数据插入成功.
接下来我们来设置@Param
属性,那么#{}
就需要用参数.属性的方式来获取.
- 返回主键
Insert语句默认返回的是受影响的行数,但是有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入的数据的id.如果想要拿到自增id,需要在Mapper接口的方法上添加⼀个Options的注解.
@Options(useGeneratedKeys =true,keyProperty ="id")@Insert("insert into userinfo (id,username,password,age,gender,phone) values ("+"#{id},#{username},#{password},#{age},#{gender},#{phone})")publicIntegerinsertUser2(UserInfouserInfo);
其中useGeneratedKeys表示的是是自增?keyProperty表示的是哪个字段自增?
@TestvoidinsertUser2(){UserInfouserInfo =newUserInfo();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.下面我们观察数据库:
2.4 删(Delete)
sql语句:delete from userinfo where id=6
.
把sql语句中的常量替换为动态参数.
@Delete("delete from userinfo where id = #{id}")publicIntegerdeleteUser1(Integerid);
@TestvoiddeleteUser1(){System.out.println(userInfoMapper.deleteUser1(6));}
查看数据库,我们看到id=6的数据被删除掉了.
2.5 改(Update)
sql语句:update userinfo set username="zhaoliu" where id=5
把SQL中的常量替换为动态的参数
Mapper接口:
@Update("update userinfo set username = #{username} where id = #{id}")publicIntegerupdateUser(UserInfouserInfo);
@TestvoidupdateUser(){UserInfouserInfo =newUserInfo();userInfo.setId(5);userInfo.setUsername("tianqi");System.out.println(userInfoMapper.updateUser(userInfo));}
测试运行:我们看到数据库中id为5的名字被改成了tianqi.
2.6 查(select)
我们在上面查询的时候,发现有几个字段是没有赋值的.只有Java对象属性和数据库字段⼀模⼀样时,才会进行赋值.
在我们查询所有数据的时候,我们发现,对象的创建时间,更新时间,删除逻辑数字都是null.
原因:
当自动映射查询结果的时候,MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性(忽略大小写).这意味着如果发现了ID列和id属性,MyBatis会将列ID的值赋给id属性.但是我们的创建时间,更新时间,删除逻辑数字,在数据库中是蛇形结构的名字,而在Java类中是小驼峰的格式.
下面是解决办法:
3.6.1 起别名
在sql语句中,给列名起别名,保持别名和实体类属性名一样.
@Select("select id, username, `password`, age, gender, phone, "+"delete_flag as deleteFlag,"+"create_time as createTime, update_time as updateTime from userinfo")publicList<UserInfo>selectAllUser2();
当sql语句太长的时候,我们可以使用+进行拼接.
3.6.2 结果映射
@Results({@Result(column ="delete_flag",property ="deleteFlag"),@Result(column ="create_time",property ="createTime"),@Result(column ="update_time",property ="updateTime")})@Select("select * from userinfo")publicList<UserInfo>selectAllUser3();
@Results
注解中可以用大括号括起多个@Result
映射,@Result
前面的参数是表的字段名,后面是Java类的属性.也就是字段与属性的映射关系.
@TestvoidselectAllUser3(){List<UserInfo>list =userInfoMapper.selectAllUser3();for(UserInfouserInfo: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")publicList<UserInfo>selectAllUser3();@ResultMap(value ="resultMap1")@Select("select * from userinfo where id = #{id}")publicUserInfoselectUser5(Integerid);
@ResultMap
注解中的value的值和上面映射的id名字一样.方可复用.
2.6.3 开启驼峰命名与蛇形命名转换(推荐做法)
通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java属性⼀般遵循驼峰命名法约定.
为了在这两种命名方式之间启用自动映射,需要将mapUnderscoreToCamelCase
设置为true.我们需要在配置文件中配置.
mybatis:configuration:map-underscore-to-camel-case:true#配置驼峰⾃动转换
转换规则:abc_xyz=>abcXyz蛇形转换为小驼峰格式.
Java代码不做任何处理:
@Select("select * from userinfo")publicList<UserInfo>selectAllUser();
我们看到原来没有赋值上的属性被赋值了.
mysql表的设计规范:
自增id,创建日期,更新日期,这几个字段即使业务没有需求,也必须有.
3. MyBatis XML配置文件
MyBatis的开发方式从大方向上分为两种,除了我们上面展示的使用注解的方式,我们还可以通过xml配置文件来开发.
使⽤Mybatis的注解方式,主要是来完成⼀些简单的增删改查功能.如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中.
这种开发方式大致分为两步:
- 配置数据库连接字符串和MyBatis.
- 写持久层代码
3.1 配置连接字符串和MyBatis
此步骤需要进行两项设置,数据库连接字符串设置和指定MyBatis的XML文件.
# 数据库连接配置 spring:datasource:url:jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername:root password:root driver-class-name:com.mysql.cj.jdbc.Driver# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件 mybatis:mapper-locations:classpath:mapper/**Mapper.xml
mapper-locations
中的value值,classpath
表示的是项目中的resource目录.mapper
表示的是一个自定义目录.一般我们使用xml操作数据库的代码都会单独放在一个mapper目录中,之后**Mapper.xml
表示的是以这个结尾的xml文件就是操作数据库的xml文件.
3.2 持久层代码
持久层代码分为两部分:
- 方法定义: Interface
- 方法实现: xxx.xml
3.2.1 添加mapper接口
packagecom.jrj.mybatis.mapper;importcom.jrj.mybatis.UserInfo;importorg.apache.ibatis.annotations.Mapper;importjava.util.List;@MapperpublicinterfaceUserInfoXMLMapper{publicList<UserInfo>selectAllUser1();}
3.2.2 添加UserInfoXMLMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.jrj.mybatis.mapper.UserInfoXMLMapper"></mapper>
mapper标签中加的是带有@Mapper
注解接口的路径,即想要通过MyBatis操作数据库的Mapper接口.
查询所有用户的具体实现:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.jrj.mybatis.mapper.UserInfoXMLMapper"><selectid="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这样的关键字的时候,插件会自动根据名字自动生成标签类型.如果没有,需要在生成的时候进行选择.
3.2.3 单元测试
进行单元测试的时候,我们可以在Mapper上点击右键–>生成–>测试–>勾选想要测试的方法,就会在test目录下自动生成测试类和测试方法,其中测试方法的返回值必须为void.
测试代码如下:
@TestvoidselectAllUser1(){List<UserInfo>list =userInfoXMLMapper.selectAllUser1();for(UserInfouserInfo:list){System.out.println(userInfo);}}
测试结果:
当然,所谓的单元测试不一定只对Mapper中的方法进行测试,也可以对Service中,或者是Controller中的方法进行测试.
3.3 增删改查操作
3.3.1 增(Insert)
UserInfoMapper接口:
publicIntegerinsertUser(UserInfouserInfo);
xml:
<insertid="insertUser">insert into userinfo (id,username,password,age,gender,phone) values (#{id},#{username},#{password},#{age},#{gender},#{phone})</insert>
测试代码:
@TestvoidinsertUser(){UserInfouserInfo =newUserInfo();userInfo.setId(8);userInfo.setUsername("zhubajie");userInfo.setAge(22);userInfo.setPassword("6666666");userInfo.setGender(0);userInfo.setPhone("487362849326");userInfoXMLMapper.insertUser(userInfo);}
运行结果:
与注解实现类似,要是在形参的前面加上@Param
注解的话,在sql语句中的#{}
就必须使用对象名.属性名来访问.
Mapper接口:
publicIntegerinsertUser(@Param("userinfo")UserInfouserInfo);
xml:
<insertid="insertUser">insert into userinfo (id,username,password,age,gender,phone) values (#{userinfo.id},#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})</insert>
3.3.2 删(Delete)
3.3.3 改(Update)
删和改和上面的增是相同的道理,这里我们不再赘述.
3.3.4 查(select)
同样,使用xml进行查询的时候,同样也存在sql字段名和类属性对应不上的问题.
xml:
<selectid="selectAllUser1"resultType="com.jrj.mybatis.UserInfo">select * from userinfo</select>
Mapper接口:
publicList<UserInfo>selectAllUser1();
运行结果:
我们看到一些字段名仍然没有被赋值.
解决办法与注解类似:
- 起别名
- 结果映射
- 开启驼峰与蛇形转换.
1,3 与注解一样,不再赘述.下面我们来看一下结果映射法如何解决字段与属性映射以及主键问题.
xml:
<resultMapid="Map"type="com.jrj.mybatis.UserInfo"><idcolumn="id"property="id"></id><resultcolumn="delete_flag"property="deleteFlag"></result><resultcolumn="create_time"property="createTime"></result><resultcolumn="update"property="updateTime"></result></resultMap><selectid="selectAllUser1"resultType="com.jrj.mybatis.UserInfo"resultMap="Map">select * from userinfo</select>
解释:
4. 其他查询操作
4.1 多表查询(联合查询)
多表查询和单表查询类似,只不过就是sql不同而已,具体sql可参考:
https://lilesily12385.blog.csdn.net/article/details/137519405
4.1.1 准备数据,创建对应的实体类
上面建了⼀张用户表,我们再来建⼀张文章表,进行多表关联查询.
文章表的uid,对应用户表的id.
- 准备数据
-- 创建⽂章表 DROPTABLEIFEXISTSarticleinfo;CREATETABLEarticleinfo (id INTPRIMARYKEYauto_increment,title VARCHAR(100)NOTNULL,content TEXTNOTNULL,uid INTNOTNULL,delete_flag TINYINT(4)DEFAULT0COMMENT'0-正常, 1-删除',create_time DATETIMEDEFAULTnow(),update_time DATETIMEDEFAULTnow())DEFAULTcharset'utf8mb4';-- 插⼊测试数据 INSERTINTOarticleinfo (title,content,uid )VALUES('Java','Java正⽂',1);
- 创建对应model
packagecom.jrj.mybatis;importlombok.Data;importjava.util.Date;@DatapublicclassArticleInfo{privateIntegerid;privateStringtitle;privateStringcontent;privateIntegeruid;privateIntegerdeleteFlag;privateDatecreateTime;privateDateupdateTime;}
4.1.2 查询数据
需求,根据uid查询作者的名称等相关信息.
sql:
SELECTta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender FROMarticleinfo ta LEFTJOINuserinfo tb ONta.uid =tb.id WHEREta.id =1
补充实体类:
@DatapublicclassArticleInfo{privateIntegerid;privateStringtitle;privateStringcontent;privateIntegeruid;privateIntegerdeleteFlag;privateDatecreateTime;privateDateupdateTime;privateStringusername;privateIntegerage;privateIntegergender;}
接口定义:
@MapperpublicinterfaceArticleInfoMapper{@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}")publicArticleInfoselectArticle(Integerid);}
测试代码:
@SpringBootTestclassArticleInfoMapperTest{@AutowiredpublicArticleInfoMapperarticleInfoMapper;@TestvoidselectArticle(){System.out.println(articleInfoMapper.selectArticle(1));}}
运行结果:
一般情况下,我们不会轻易使用联合查询,因为它是一种慢查询,会影响集群的性能.我们一般会把联合查询的sql拆分成多个单表查询.
当然有些情况下可以使用多表查询,这些情况对于性能要求都比较小:比如内部员工使用的系统,B端项目(平台的商家端).
4.2 #{} 和 ${}
在MyBatis中,参数赋值的方法不仅仅只有#{}
,而且还有${}
,下面我们来看一下二者的区别.
4.2.1 使用
- 先看Integer类型的参数
@Select("select * from userinfo where id = #{id}")publicUserInfoselectUser(Integerid);
观察日志:
我们发现,通过次接口输出的sql语句是:select * form userinfo where id = ?
我们输⼊的参数并没有在后面拼接,id的值是使用? 进行占位.这种SQL我们称之为"预编译SQL".预编译sql我们在mysql中的JDBC编程中有提到过.就是把参数的值直接赋给了前面的id.
把#{}
替换为${}
之后再次观察打印日志:
@Select("select * from userinfo where id = ${id}")publicUserInfoselectUser(Integerid);
我们看到,参数直接拼接到了sql语句中.
2. 接下来就是String类型的参数
@Select("select * from userinfo where username = #{name}")publicUserInfoselectUser6(Stringname);
观察打印日志:
把#{}
换为${}
再观察打印日志.
@Select("select * from userinfo where username = ${name}")publicUserInfoselectUser6(Stringname);
观察日志:
我们发现,这次的结果依然是把参数直接拼接在了sql中,但是字符串作为参数的时候需要添加引号 ‘’ ,使
⽤ ${}
不会拼接引号‘’ ,导致程序报错.
我们修改代码:
@Select("select * from userinfo where username = '${name}'")publicUserInfoselectUser6(Stringname);
再次运行,正常返回:
从上面两个例子可以总结:
#{} 使用的是预编译SQL,通过?占位的方式,提前对SQL进行编译,然后把参数赋值到SQL语句中.#{} 会根据参数类型,自动拼接引号’’ .${} 会直接进行字符替换,直接把参数拼接到sql中,⼀起对SQL进行编译.如果参数为字符串,需要加上引号 ‘’.
4.2.2 #{} 和 ${}的区别(高频面试题)
#{}和${}的区别就是预编译SQL和即时SQL的区别.
#{}
相对于${}
性能更高.
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输⼊的参数不同),省去了解析优化等过程,以此来提高效率.#{}
更安全(防止sql注入)
SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句的本意,以达到执行代码对服务器进行攻击的方法.由于没有对用户输入进行充分检查,而SQL⼜是拼接而成,在用户输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的目的,也可以完成恶意攻击.
我们下面来举一个sql注入的例子:sql注入代码: ’ or 1='1
@Select("select * from userinfo where username = '${name}'")publicUserInfoselectUser6(Stringname);
sql注入后:
@TestvoidselectUser6(){System.out.println(userInfoMapper.selectUser6("' or 1='1"));}
运行结果:
sql就返回所有用户的信息.并不是我们想要的信息.所以我们尽量使用#{}
的方式.
4.3 排序功能
即使${}
会有sql注入的风险.有些情况下,${}
还是有他自己的作用.比如对数据进行排序.
Mapper实现:把数据根据id降序排名.
@Select("select * from userinfo order by id ${sort}")publicList<UserInfo>selectUser7(Stringsort);
@TestvoidselectUser7(){System.out.println(userInfoMapper.selectUser7("desc"));}
返回了正确的结果;
==如果把KaTeX parse error: Expected 'EOF', got '#' at position 3: 改成#̲,就会自动对字符串加上``' …{}``.
4.4 like查询
like使用#{}
会报错.会给字符串加上单引号.
@Select("select * from userinfo where username like '%#{name}%'")publicUserInfoselectUser8(Stringname);
@TestvoidselectUser8(){System.out.println(userInfoMapper.selectUser8("dmi"));}
虽然使用${}
会成功,但是存在sql注入问题,不可以直接使用.所以我们通过sql中的一个函数concat()来处理,它是一个字符串拼接函数.
@Select("select * from userinfo where username like concat('%',#{name},'%')")publicUserInfoselectUser8(Stringname);
运行成功: