上章回顾
- 我们在MyBatis(一)中介绍了有关MyBatis的相关概念:
- 持久层框架
- 几乎避免了所以JDBC代码和手动设置参数以及获取结果集
- 使用了ORM思想,实现了结果集的封装
【ORM:object Relational Mapping 对象关系映射】 - 把数据库表和实体类以及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
- 我们在MyBatis(二)中介绍了有关MyBatis的增删改查
本章内容
接下来,我们来看看,如何在Mapper.xml文件中,对数据进行增删改查。这也是最常用的增删改查,通过注解的方式只能编写一些比较简单的SQL,但在现实业务中,我们会用比较复杂的SQL对数据进行操作。
本章学习的重点是如何书写动态SQL!
步骤:
- 编写映射接口UserMapper
- 声明方法(增删改查的方法声明)
- 编写对应的映射文件UserMapper.xml
- 写入符合业务的SQL语句
- 编写单元测试类,测试功能
目录结构:
对应代码:
1.新增用户【int addUser(User user);
】
- 在UserMapper中申明一个插入数据的方法:
// 添加一个用户
int addUser(User user);
- 在UserMapper.xml中,编写该方法的SQL
<insert id="addUser" parameterType="user">
insert into user(id,username,password) values (#{id},#{username},#{password})
</insert>
- 测试该方法
@Test
public void addUserTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(1);
user.setUsername("zhangsan");
user.setPassword("zs123456");
mapper.addUser(user);
sqlSession.close();
}
我们查看一下日志信息:
已经完成了插入操作,我们来看看数据库
数据库中插入一条数据,插入操作成功!
2.删除用户【int removeUser(int id);
】
- 在UserMapper中申明删除用户的方法
// 删除一个用户
int removeUser(int id);
- 在UserMapper.xml中,编写该方法的SQL
<delete id="removeUser" parameterType="int">
delete from user where id = #{id}
</delete>
- 测试该方法
@Test
public void deleteUserTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.removeUser(1);
sqlSession.close();
}
我们来查看一下日志:
可以看到,这里执行了一条删除语句!
我们再来看看数据库中的数据:
id为1的数据被删除了!删除用户操作成功!
★3.修改用户信息【int alterInfo(Map map);
】
- 在UserMapper中申明一个修改方法:
// 修改用户信息
int alterInfo(Map map);
- 在UserMapper.xml中,编写该方法的SQL
<update id="alterInfo" parameterType="map">
update user
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="password!=null">
password = #{password}
</if>
</set>
where id = #{id}
</update>
- 测试该方法
@Test
public void updateUserInfo(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("id",2);
map.put("username","zhangsan");
map.put("password","132");
mapper.alterInfo(map);
sqlSession.close();
}
我们来查看一下日志:
可以看到,这里id为2的用户,用户名更改为:zhangsan,密码更改为:132
再来查看一下数据库:
id为2的用户信息被更改了!修改用户信息操作成功!
注意:
这里使用了一对<set></set>
标签,主要作用是:当需要更改的字段只有一个时,可以自动去掉“逗号”。
如,这里把username后面的逗号“去掉”了。
这里就把password前面的逗号“去掉”了
这样就会更灵活,完成更多操作!
★★★4.查询用户
4.1 查询全部用户【List<User> queryAll();
】
- 在UserMapper中申明查询全部用户数据的方法
// 查询全部用户信息
List<User> queryAll();
- 在UserMapper.xml中,编写该方法的SQL
<select id="queryAll" resultType="user">
select * from user
</select>
- 测试该方法
@Test
public void findAllUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.queryAll();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
查看日志结果:
这是一个比较简单的查询,直接无条件查询全部用户信息。
下面让我们来看看几个比较复杂的查询!
4.2 根据指定条件查询用户【List<User> queryUser(Map map);
】
- 在UserMapper中申明查询用户数据的方法
// 根据条件查询用户信息
List<User> queryUser(Map map);
- 在UserMapper.xml中,编写该方法的SQL
<select id="queryUser" parameterType="map" resultType="user">
select * from user
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="username!=null">
and username = #{username}
</when>
<when test="password!=null">
and password = #{password}
</when>
</choose>
</where>
</select>
- 测试该方法
@Test
public void findUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("id",2);
map.put("username","user2");
map.put("password","123");
List<User> userList = mapper.queryUser(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
输入日志信息:
这里我们可以看到,即便是有三个条件,但是如果第一个条件不为空,查询仅会按第一个条件来进行查询!
假如,没有第一个条件,会发生什么呢?
假如没有第一个id的条件,那他就会执行后面的条件,如果后面有条件,则只执行最前面的一个条件,后面的条件将不再执行。
假如,三个条件都没有,将执行什么语句呢?
正如日志所显示的那样,假如没有条件的话,就是查询全部的用户信息
注意:
这里除了使用了<select></select>
标签对以外,还使用了三个标签对:<where></where>
、<choose></choose>
、<when></when>
使用<where></where>
标签对,能自动把and去掉(和前面set标签对去掉逗号类似),使用<choose></choose>
标签对,将从下列条件中选择性执行(有点像switch-case语句),使用<when></when>
标签对,将执行满足条件的语句。
尤其注意,choose和when标签是配对使用,也就是说,要执行when标签的东西,必须要先有choose标签“包裹”在外面,类似于switch-case,有原子性,不可分割!
可能有人要问了,那我如果想查询所有条件都满足的数据,又该怎么办呢?
我们依旧用<where></where>
标签对,但是里面做一点点小改动!
<select id="queryUser" parameterType="map" resultType="user">
select * from user
<where>
<if test="id!=null">
id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</where>
</select>
我们把之前的choose和when全部替换成<if></if>
标签对,就可以实现“查询满足全部条件的数据”了!
这里再针对choose和when标签做一点点补充:
引入<otherwise></otherwise>
标签对,该标签对含义是:无论最后一个条件是否存在,都会将其作为条件来执行,但是如果前面有条件的时候,就会执行前面的(和之前一样)。
4.3 根据集合中的内容查询用户数据【List<User> queryForeach(Map map);
】
- 在UserMapper中申明查询用户数据的方法
// 根据集合中的字段内容查询用户信息
List<User> queryForeach(Map map);
- 在UserMapper.xml中,编写该方法的SQL
<select id="queryForeach" parameterType="map" resultType="user">
select * from user
<where>
<foreach collection="uid" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
- 测试该方法:
@Test
public void findUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
HashMap map = new HashMap();
map.put("uid",arrayList);
List<User> userList = mapper.queryForeach(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
日志显示的内容:
从日志显示的结果来看,该SQL的前面拼了一个“(”,中间用“or”分割,后面拼了一个“)”,该操作可以用于:当需要查询的字段值有多种情况时,将满足其中任意条件的结果返回。
关于foreach的几点补充:
当传入参数为数组或者集合时需要通过<foreach></foreach>
标签进行遍历
1、首先在实体类中定义一个集合或者数组 比如 private List<Integer> ids;
2、在映射文件中<foreach collection="ids" item="ids" item="user_id" open="(" close=")" seperator="or"><foreach/>
collection:指定输入对象中集合属性
item:每次遍历生成的对象
open:开始遍历时拼接的串
close:结束遍历时两个对象需要拼接的串
★★4.4 利用sql片段进行查询【List<User> queryAll2(Map map);
】
描述:当有大量标签重复时,为了避免代码冗余,我们可以将其作为一个SQL片段,插入到需要的标签对中。
- 在UserMapper中申明查询用户数据的方法
// 使用SQL片段进行查询
List<User> queryAll2(Map map);
- 在UserMapper.xml中,编写该方法的SQL
<sql id="name-pwd">
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</sql>
<select id="queryAll2" parameterType="map" resultType="user">
select * from user
<where>
<include refid="name-pwd"/>
</where>
</select>
这里分成了两部分,容易重复的代码放在<sql></sql>
标签中,并给其一个id,如果需要引用,使用<include refid="[sql标签中的id]"/>
,即可调用!
- 测试该方法
@Test
public void findAllUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("username","zhangsan");
List<User> userList = mapper.queryAll2(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
日志显示的结果:
该方法可以根据传入的参数进行查询操作,当传入两个参数时:
因为使用的是标签,中间使用“and”连接,所以当传入两个参数时,查询条件是必须同时满足这两个条件!
建议:理解标签的使用,尤其掌握和查询有关的操作!
5.分页查询【List<User> queryLimit(Map map);
】
- 在UserMapper中申明分页查询用户数据的方法
// 分页查询
List<User> queryLimit(Map map);
- 在UserMapper.xml中,编写该方法的SQL
<select id="queryLimit" parameterType="map" resultType="User">
select * from user limit #{startIndex},#{pageSize}
</select>
- 测试该方法
@Test
public void queryUserLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("startIndex",0);
map.put("pageSize",3);
List<User> userList = mapper.queryLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
查看日志信息:
该SQL是从0(也就是第一行)开始查询,查询的步长为3(一次查3行数据)
我们来看看数据库表的信息。
是符合要求的查询结果!说明我们的分页方法操作成功!
补充:这里我们除了使用resultType,我们还可以使用resultMap!
修改UserMapper.xml中的代码:
<resultMap id="UserMapper" type="user">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
<select id="queryLimit" parameterType="map" resultMap="UserMapper">
select * from user limit #{startIndex},#{pageSize}
</select>
查看日志:
使用resultMap和resultType的区别是:
resultType
resultType可以把查询结果封装到pojo类型中,但必须pojo类的属性名和查询到的数据库表的字段名一致。
如果sql查询到的字段与pojo的属性名不一致,则需要使用resultMap将字段名和属性名对应起来,进行手动配置封装,将结果映射到pojo中。
resultMap
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
6.模糊查询【List<User> fuzzyQuery(String username);
】
- 在UserMapper中申明模糊查询用户数据的方法
// 模糊查询
List<User> fuzzyQuery(String username);
- 在UserMapper.xml中,编写该方法的SQL
<select id="fuzzyQuery" parameterType="String" resultType="user">
select * from user where username like "%"#{username}"%"
</select>
- 测试该方法
@Test
public void fuzzyQueryUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.fuzzyQuery("user");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
查看日志结果:
说明:该SQL是查询username字段的数据中,包含“user”的数据。
在mysql中,有一种函数(concat()
)可以通过拼接字符串达到模糊查询的目的:
修改UserMapper.xml中的代码:
<select id="fuzzyQuery" parameterType="String" resultType="user">
select * from user where username like concat(concat('%',#{username},'%'))
</select>
日志显示的结果:
这两种方法都可以实现MyBatis的模糊查询。
备注:
1.其实还可以使用’%${username}%'的方式,再拼字符,但是不能防止SQL注入
2.或者直接在java代码中传入一个"%user%",但很明显不适合真正的使用环境
。
注意:
在运行的时候可能会出现一个异常:org.apache.ibatis.binding.BindingException
1.映射文件中,namespace中的路径和映射接口路径对应,否则扫描不到
2.在pom.xml中加入<build></build>
,防止在build中配置resources来防止我们资源导出失败问题。
<!-- 在build中配置resources来防止我们资源导出失败问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
3.引入别名的时候,是为实体类的路径取别名
<typeAliases>
<package name="com.demo.pojo"/>
</typeAliases>
其他
1.Lombok插件的使用说明
1.1 说明:
我们在创建实体类的时候,会创建实体类的构造方法(有参构造和无参构造),和各种get以及set方法,虽然现在可以通过idea的快捷键(alt+insert),自动导入
但是,大量的get和set方法会让实体类代码量变多,而且一旦有新的字段(变量)出现,就要再添加新的get,set以及有参构造方法。
那么,有什么方法可以减少重复劳动,减少代码量呢?Lombok来了!
1.2 Lombok使用
使用步骤:
1.在pom.xml文件中,导入Lombok的依赖。
<!-- Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
2.在实体类中加入注解
package com.demo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author: seh
* @date: 2020/10/25 9:04
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
}
加入三个注解,就能把原先大段的get和set方法以及有参、无参构造方法省略。
@Data
、@NoArgsConstructor
、@AllArgsConstructor
我们来测试一下!
之前写的模糊查询的操作没有问题!操作成功!
那么为什么加了Lombok的依赖后,使用三个注解就可以实现?
点击idea中的【structrure】
原来,注解内部已经封装好了,@Data
包括了这些方法
@NoArgsConstructor
、@AllArgsConstructor
就是添加有参构造和无参构造了!
可能大家觉得Lombok插件“太棒了!”
但实际上,如果使用了这个插件,对于不清楚内部构造的人来说,根本看不懂实体类。
更重要的是:
如果你想确认某个set或get方法是否在程序中被调用,你无法找到哪里使用的,但是我更认为这样的操作是违背了bean使用的初衷,bean尤其数据库和java类的映射bean,java对bean的定义和使用就是无参数的构造方法和set和get方法,而不应该在bean中处理任何和业务有任何关系的逻辑。
所以,对于Lombok的评价,仁者见仁,智者见智吧!
2.log4j日志说明
这是Mybatis官网对于日志的介绍:
https://mybatis.org/mybatis-3/zh/logging.html
2.1 什么是log4j?
-
Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
-
我们也可以控制每一条日志的输出格式
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
-
这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
我们前面介绍过标准的日志输出(STDOUT_LOGGING),接下来我们来看看如何在项目中配置log4j日志。
2.2配置log4j的步骤:
1.在pom.xml中配置log4j的依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.在mybatis-config.xml加入setting设置
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
setting的位置千万不能错!
3.在resources目录下创建一个log4j的配置文件
log4j.properties
文件的配置内容如下:
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/tomcat.txt
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
这样配置完成以后就可以运行了!
运行完以后,在项目的目录树中出现一个新的log目录
日志目录和日志文件可以在log4j.properties文件中设置。
log4j.appender.file.File=./log/tomcat.txt
2.3 日志级别
logger.info("info:进入了log4j");
logger.debug("debug:进入了log4j");
logger.error("error:进入了log4j");
2.3.1 测试日志级别
static Logger logger = Logger.getLogger(TestCheck.class);
@Test
public void testLog4j(){
logger.info("info:进入了log4j");
logger.debug("debug:进入了log4j");
logger.error("error:进入了log4j");
}
2.3.2 控制台信息
2.3.3 日志文件信息
由此可见,运行的日志会一直往后添加(默认append是true)
我们设置成false 就不追加了 直接覆盖前面的内容,或者手动清除日志。
只需要在log4j.properties文件中加入如下的语句:
log4j.appender.file.Append = false
运行结果:
关于MyBatis的内容到这里就结束了!分页插件PageHelper、mybatis-plus等内容后期有需要再更新。(可能之后还会更新一个Redis的内容)
之后会更新Spring、SpringMVC以及SSM整合的项目,请持续关注!(ps:终于写完了QAQ)
更多内容,请关注公众号:
源码的链接我会放评论区。
创作不易,如果觉得还可以的话,给个三连,拜托了!
源码地址:
github地址:https://github.com/Holmes-SiEnhao/SSM-.git
gitee地址:https://gitee.com/sienhao/ssm-framework-learning.git