一、引言
在当今的软件开发环境中,数据库的使用已经成为了一项基础且必不可少的技能。而在处理数据库相关的任务时,SQL查询语句无疑是最为常用的工具之一。然而,随着应用程序的复杂性不断增加,我们往往需要编写更加复杂的SQL查询语句以满足需求。这就引出了我们今天要讨论的主题——MyBatis。
在这篇文章中,我们将深入探讨MyBatis的两个重要特性:动态SQL和模糊查询。动态SQL是MyBatis的一个强大功能,它允许我们在XML映射文件中编写动态生成的SQL语句。模糊查询则是我们在处理大量数据时常用的一种查询方式,它可以帮助我们快速地找出符合特定条件的记录。
此外,我们还将讨论MyBatis的结果映射功能。结果映射是将数据库查询结果映射到Java对象的过程,它是MyBatis的核心功能之一。通过结果映射,我们可以将复杂的数据库表结构转换为Java中的对象,使得程序的设计更加直观和简单。
掌握MyBatis的这些高级特性,不仅可以提高我们的开发效率,也可以让我们在面试中脱颖而出。因此,无论你是已经有一定经验的开发者,还是刚刚入门的学习者,都值得花时间去学习和理解这些知识。在接下来的文章中,我们将详细介绍这些特性的使用方法和注意事项,希望对你有所帮助。
二、MyBatis动态SQL
2.1.if元素使用
在以往的编写中我们肯定写过这样的代码
update t_mvc_book bname=?,btype=?,bprice=?,..... where bid=?
假如我们前台jsp没有传参数bname到后台,那么会怎么样?
update t_mvc_book bname=null,btype='科幻小说',bprice=9.9,..... where bid=7
就会造成这样的结果,原本我们不传递参数只是不想改变原有的字段值,没成想不传反而变为null
有些人就会想那我传个参数来行不行呢?行,但是没必要。如果我们使用Mybatis里面的动态SQL,这个问题就迎刃而解了,它会把我们的sql语句变为xml的方式,通过if标签判断,如果为空就不会拼接判断条件里的内容。
<update id="updateByPrimaryKeySelective" parameterType="com.csdn.xw.model.Book" > update t_mvc_book <set > <if test="bname != null" > bname = #{bname,jdbcType=VARCHAR}, </if> <if test="price != null" > price = #{price,jdbcType=REAL}, </if> </set> where bid = #{bid,jdbcType=INTEGER} </update>
2.2.foreach元素使用
我们再来假设一个场景: 我们写代码的过程中肯定写过对某个事物的批量删除
可能是这样遍历数组然后调用删除方法
for int id :ids
orderItemsDao.delete(id)
也可能是拼接的方式将需要删除的id做个拼接
delete from t_oa_order where id in(...)
如果是使用mybatis我们就可以这样写
<select id="selectByBids" resultType="com.csdn.xw.model.Book" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from t_mvc_book where bid in <foreach collection="bids" item="bid" open="(" close=")" separator=","> #{bid} </foreach> </select>
现在来测试一下
BookMapper
List<Book> selectByBids(@Param("bids") List bids);
BookBiz
List<Book> selectByBids(List bids);
BookBizImpl
@Override public List<Book> selectByBids(List bids) { return bookMapper.selectByBids(bids); }
demo测试类
@Test public void selectByBids() { System.out.println("测试的查询的方法"); List<Integer> integers = Arrays.asList(new Integer[]{55, 56, 57, 58}); bookBiz.selectByBids(integers).forEach(System.out::println); }
测试结果:
三、MyBatis模糊查询
为了突出我们的效果展示,我们增加一个配置文件log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- status : 指定log4j本身的打印日志的级别.ALL< Trace < DEBUG < INFO < WARN < ERROR < FATAL < OFF。 monitorInterval : 用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s. --> <Configuration status="WARN" monitorInterval="30"> <Properties> <!-- 配置日志文件输出目录 ${sys:user.home} --> <Property name="LOG_HOME">/root/workspace/lucenedemo/logs</Property> <property name="ERROR_LOG_FILE_NAME">/root/workspace/lucenedemo/logs/error</property> <property name="WARN_LOG_FILE_NAME">/root/workspace/lucenedemo/logs/warn</property> <property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t-%L] %-5level %logger{36} - %msg%n</property> </Properties> <Appenders> <!--这个输出控制台的配置 --> <Console name="Console" target="SYSTEM_OUT"> <!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --> <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" /> <!-- 输出日志的格式 --> <!-- %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间 %p : 日志输出格式 %c : logger的名称 %m : 日志内容,即 logger.info("message") %n : 换行符 %C : Java类名 %L : 日志输出所在行数 %M : 日志输出所在方法名 hostName : 本地机器名 hostAddress : 本地ip地址 --> <PatternLayout pattern="${PATTERN}" /> </Console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 --> <!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true --> <File name="log" fileName="logs/test.log" append="false"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size, 则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 --> <RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/info.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <Policies> <!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。 modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am. --> <!-- 关键点在于 filePattern后的日期格式,以及TimeBasedTriggeringPolicy的interval, 日期格式精确到哪一位,interval也精确到哪一个单位 --> <!-- log4j2的按天分日志文件 : info-%d{yyyy-MM-dd}-%i.log --> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <!-- SizeBasedTriggeringPolicy:Policies子节点, 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小. --> <!-- <SizeBasedTriggeringPolicy size="2 kB" /> --> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${WARN_LOG_FILE_NAME}/warn.log" filePattern="${WARN_LOG_FILE_NAME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="2 kB" /> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20" /> </RollingFile> <RollingFile name="RollingFileError" fileName="${ERROR_LOG_FILE_NAME}/error.log" filePattern="${ERROR_LOG_FILE_NAME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd-HH-mm}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <Policies> <!-- log4j2的按分钟 分日志文件 : warn-%d{yyyy-MM-dd-HH-mm}-%i.log --> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <!-- <SizeBasedTriggeringPolicy size="10 MB" /> --> </Policies> </RollingFile> </Appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 --> <Loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息 --> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <!-- 第三方日志系统 --> <logger name="org.springframework" level="ERROR" /> <logger name="org.hibernate" level="ERROR" /> <logger name="org.apache.struts2" level="ERROR" /> <logger name="com.opensymphony.xwork2" level="ERROR" /> <logger name="org.jboss" level="ERROR" /> <!-- 配置日志的根节点 --> <root level="all"> <appender-ref ref="Console" /> <appender-ref ref="RollingFileInfo" /> <appender-ref ref="RollingFileWarn" /> <appender-ref ref="RollingFileError" /> </root> </Loggers> </Configuration>
log4j2.xml文件应该位于项目的类路径根目录下,即src/main/resources目录中。
小贴士:
Log4j2的配置文件,用于配置日志输出的格式、级别、目标等信息。Log4j2不再支持Log4j 1.x中的.properties文件的配置方式,而是支持通过json、xml或者jsn的方式进行配置。
具体来说,log4j2.xml文件中包含了以下几个部分:
- Configuration:配置信息区域,包括Appenders、Loggers等。
- Appender:输出目标区域,包括控制台、文件、数据库等。
- Logger:日志记录区域,包括包名、类名、日志级别等。
在MyBatis中有三种模糊查询的方法分别是:
①使用#{字段名}
<select id="like1" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" > select <include refid="Base_Column_List" /> from t_mvc_book where bname like #{bname} </select>
测试结果:
②使用${字段名}
<select id="like2" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" > select <include refid="Base_Column_List" /> from t_mvc_book where bname like ${bname} </select>
测试结果:
③使用concat{'%',#{字段名},'%'}
<select id="like3" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" > select <include refid="Base_Column_List" /> from t_mvc_book where bname like concat('%',#{bname},'%') </select>
测试结果:
总结
想必你也看出了,这三种的不同之处,下面我来给你总结一下。
mybatis中的$与#的区别
①$是占位符传参,#是预处理SQL
②外在形式:$传参不带引号,#自带引号
可能你还没感受到有没有引号没什么太大的区别,下面我来给你演示一下
假如我要根据部门编号查出该部门的所有人员信息,利用$与#来做测试
#:select*from t_oa_employee where deptid=#{deptid}
select*from t_oa_employee where deptid='10011'
$:select*from t_oa_employee where deptid=${deptid}
select*from t_oa_employee where deptid=10011
这么一看好像确实没有什么区别,那如果前台恶意传参呢?
http://localhost:8080/employee?deptid=10011 or 1=1
那这样不就把全公司的人员信息都查出来了
③$传参存在sql注入,#不存在
④$可以做动态列,完成动态sql的开发
我们开发过程中肯定有遇到过,当前字段不满足我们需求,然后我们修改数据库字段的时候,如果改成正确还好,如果错误那就芭比Q了,动态列就可以做到传入的参数当作需要查询的列来操作例如:
假设我们有一个表名为user_info,包含字段id、name、age、gender等,现在需要根据传入的参数来动态生成查询的列,可以使用如下的语句
<select id="getUserInfo" resultType="map"> SELECT ${columnName}, ${columnAge}, ${columnGender} FROM user_info </select>
其中,columnName、columnAge、columnGender分别代表需要查询的列名,可以在Java代码中定义一个Map变量,将列名作为key,对应的值作为value传入即可。例如
Map<String, Object> columnMap = new HashMap<>(); columnMap.put("columnName", "name"); columnMap.put("columnAge", 18); columnMap.put("columnGender", "男");
然后将columnMap传入MyBatis的mapper中调用即可。
最后小编还是建议大家一般能用 # 的就别用 $ ,最常用的还是第三种。
四、MyBatis结果映射
在使用MyBatis中拥有多个场景,返回的结果是多样的,resultType/resultMap
①返回单表的对应的实体类,仅有一个查询结果,可以用resultType/resultMap。
②返回单表的对应的实体类,有多个查询结果,可以用resultType/resultMap。
③返回多表对应结果,仅有一个查询结果,通常用resultType也可以用resultMap。
④返回多表对应结果,有多个查询结果,通常用resultType也可以用resultMap。
⑤返回单个列段,仅有一个查询结果,就用resultType。
⑥返回单个列段,有多个查询结果,就用resultType。
总结就是在Mybatis中结果集的处理分为两种:
resultMap:适合使用返回值是自定义实体类的情况
resultType:适合使用返回值的数据类型是非自定义的,即jdk的提供的类型
如果是单表的情况下,resultType与resultMap都可以使用。
1 使用resultMap返回映射关系,指的是实体类与数据库字段的关系
2 使用resultType返回List
3 使用resultType返回单个对象
4 使用resultType返回List【适用于多表查询返回结果集】
5 使用resultType返回Map【适用于多表查询返回单个结果集】
4.1.案例演示
4.1.1.resultType进行结果映射
假设我们有一个用户表user,包含以下字段:id、name、age、gender、email。现在需要根据这些字段查询用户信息并返回一个User对象。
首先,我们需要定义一个User类,用于存储查询结果:
public class User { private int id; private String name; private int age; private String gender; private String email; // getter和setter方法省略 }
然后,在MyBatis的mapper文件中,我们可以使用resultType来映射查询结果到User对象上。具体配置如下:
<select id="getUserById" resultType="com.example.User"> SELECT id, name, age, gender, email FROM user WHERE id = #{id} </select>
在上面的配置中,我们使用了resultType属性来指定查询结果映射到的Java对象的全限定名。这样,当执行查询语句时,MyBatis会自动将查询结果映射到指定的Java对象上。
4.1.2.resultMap进行结果映射
假设我们有一个订单表order,包含以下字段:id、user_id、product_id、price、quantity。现在需要根据这些字段查询订单信息并返回一个Order对象。
首先,我们需要定义一个Order类,用于存储查询结果:
public class Order { private int id; private int userId; private int productId; private double price; private int quantity; // getter和setter方法省略 }
然后,在MyBatis的mapper文件中,我们可以使用resultMap来映射查询结果到Order对象上。具体配置如下:
<resultMap id="OrderResultMap" type="com.example.Order"> <id property="id" column="id" /> <result property="userId" column="user_id" /> <result property="productId" column="product_id" /> <result property="price" column="price" /> <result property="quantity" column="quantity" /> </resultMap> <select id="getOrderById" resultMap="OrderResultMap"> SELECT id, user_id, product_id, price, quantity FROM order WHERE id = #{id} </select>
在上面的配置中,我们定义了一个名为"OrderResultMap"的resultMap,它的type属性指定了要映射的Java对象的全限定名。接下来,我们为每个字段指定了对应的属性名和数据库表中的列名。最后,在查询语句中引用这个resultMap即可将查询结果映射到Order对象上。
需要注意的是,如果查询结果中的某个字段在Java对象中没有对应的属性,那么该字段将被映射为null。此外,如果查询结果中的某个字段在Java对象中有多个对应的属性,那么该字段的值将被映射为一个列表或数组。
到这里我的分享就结束了,欢迎到评论区探讨交流!!
如果觉得有用的话还请点个赞吧 ♥ ♥