一.动态sql
1.1.什么是动态SQL
动态SQL是一种在运行时构建和执行SQL语句的方法。通常情况下,SQL语句是在编译时确定的,但是动态SQL允许在程序运行时根据特定条件或者用户输入来构建SQL语句。这样可以使得SQL语句更加灵活和可变化。动态SQL可以用于实现动态的搜索、过滤和排序等功能。它在很多编程语言和数据库中都有支持,比如Java的JDBC、Python的SQLAlchemy等。需要注意的是,动态SQL在使用时需要注意防止SQL注入攻击,确保输入的数据正确地转义和验证。
1.2.动态SQL的好处
- 动态性:可以根据不同的条件和需求构建不同的SQL语句,使得查询和操作更加具有灵活性。可以根据用户的输入动态生成SQL语句,实现动态搜索、过滤、排序等功能。
- 可重用性:通过使用动态SQL,可以减少重复编写SQL语句的工作量,提高了代码的可重用性。通过使用参数化查询,可以重用同一个SQL语句的模板,只需要传入不同的参数即可。
- 性能优化:通过动态SQL,可以根据实时的数据情况进行优化。可以根据具体的条件生成不同的查询计划,从而提高查询性能。
- 维护性:动态SQL使得修改和维护SQL语句变得更加容易。当需求变化时,只需要修改生成SQL语句的代码,而不需要修改大量的静态SQL语句。
1.3.演示
根据我们自动生成的xml,接口,实体进行增加代码进行动态SQL的演示操作
自动生成的实体代码( Book )对象:
package com.junlinyi.model; public class Book { private Integer bid; private String bname; private Float price; public Book(Integer bid, String bname, Float price) { this.bid = bid; this.bname = bname; this.price = price; } public Book() { super(); } public Integer getBid() { return bid; } public void setBid(Integer bid) { this.bid = bid; } public String getBname() { return bname; } public void setBname(String bname) { this.bname = bname; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } @Override public String toString() { return "Book{" + "bid=" + bid + ", bname='" + bname + '\'' + ", price=" + price + '}'; } }
自动生成的实体接口 BookMapper:
package com.junlinyi.mapper; import com.junlinyi.model.Book; import org.apache.ibatis.annotations.Param; import java.util.List; public interface BookMapper { int deleteByPrimaryKey(Integer bid); int insert(Book record); int insertSelective(Book record); Book selectByPrimaryKey(Integer bid); int updateByPrimaryKeySelective(Book record); int updateByPrimaryKey(Book record); //增加根据多个ID(bid)查询多个数据的方法 List<Book> selectByBll(@Param("bids") List bids); }
自动生成的 BookMapper.xml 配置文件:
<?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.junlinyi.mapper.BookMapper" > <resultMap id="BaseResultMap" type="com.junlinyi.model.Book" > <constructor > <idArg column="bid" jdbcType="INTEGER" javaType="java.lang.Integer" /> <arg column="bname" jdbcType="VARCHAR" javaType="java.lang.String" /> <arg column="price" jdbcType="REAL" javaType="java.lang.Float" /> </constructor> </resultMap> <sql id="Base_Column_List" > bid, bname, price </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from t_mvc_book where bid = #{bid,jdbcType=INTEGER} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" > delete from t_mvc_book where bid = #{bid,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="com.junlinyi.model.Book" > insert into t_mvc_book (bid, bname, price ) values (#{bid,jdbcType=INTEGER}, #{bname,jdbcType=VARCHAR}, #{price,jdbcType=REAL} ) </insert> <insert id="insertSelective" parameterType="com.junlinyi.model.Book" > insert into t_mvc_book <trim prefix="(" suffix=")" suffixOverrides="," > <if test="bid != null" > bid, </if> <if test="bname != null" > bname, </if> <if test="price != null" > price, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="bid != null" > #{bid,jdbcType=INTEGER}, </if> <if test="bname != null" > #{bname,jdbcType=VARCHAR}, </if> <if test="price != null" > #{price,jdbcType=REAL}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.junlinyi.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> <update id="updateByPrimaryKey" parameterType="com.junlinyi.model.Book" > update t_mvc_book set bname = #{bname,jdbcType=VARCHAR}, price = #{price,jdbcType=REAL} where bid = #{bid,jdbcType=INTEGER} </update> <!-- 根据多个bid(编号)进行查询出多个Book(对象),bids参数需要是一个List<Integer> 集合--> <select id="selectByBll" resultType="com.junlinyi.model.Book" parameterType="java.util.List" > select <include refid="Base_Column_List" /> from t_mvc_book where bid in <foreach collection="bids" item="bid" open="(" close=")" separator=","> #{bid} </foreach> </select> </mapper>
其中的动态SQL可以根据自己的需要进行编写 :
<select id="selectByBll" resultType="com.junlinyi.model.Book" parameterType="java.util.List" > select <include refid="Base_Column_List" /> from t_mvc_book where bid in <foreach collection="bids" item="bid" open="(" close=")" separator=","> #{bid} </foreach> </select>
再将以下代码增加到增加编写的接口中:
// 封装方法 List<Book> selectByBll(List bids);
然后在实现类中增加以下代码:
// 方法实现 @Override public List<Book> selectByBll(List bids) { return bookMapper.selectByBll(bids); }
最后我们就可以在Demo测试类中进行方法调用测试
在Demo中增加以下代码进行测试 :
@Test public void Bll(){ List<Integer> bids = Arrays.asList(new Integer[]{41,42,43,45,47}); //jdk1.8的新特性写法 bookBiz.selectByBll(bids).forEach(System.out::println); //上面这个等于下面这个,上面这个方便大佬们理解 // for (Book book: bookBiz.selectByBll(bids)) { // System.out.println(book); // } }
结果:
二.#{...}与${...}的区别
2.1 #使用
2.1.1.#占位符语法
- #占位符会将传入的参数值自动进行预编译处理,可以防止SQL注入攻击。
- 使用#占位符时,Mybatis会将参数值以安全的方式替换到SQL语句中,使用JDBC的预编译语句来执行SQL查询。
- #占位符在生成SQL语句时会对参数值进行类型处理,将参数值转换为对应的JDBC类型。
- #占位符可以防止SQL注入攻击,但是无法实现动态拼接SQL片段。
2.1.2. #优点
#占位符语法在使用动态SQL时具有以下优点:
- 防止SQL注入攻击:#占位符语法会将传入的参数值进行预编译处理,将参数值转换为对应的JDBC类型。这样可以防止恶意输入对SQL语句造成的安全威胁,提高了系统的安全性。
- 参数值类型处理:#占位符语法会对参数值进行类型处理,将参数值转换为对应的JDBC类型。这样可以避免在SQL语句中手动进行类型转换的麻烦,提高了开发效率。
- 可读性和可维护性:使用#占位符语法可以使SQL语句更加清晰和可读,因为参数值被封装在占位符中,不会直接出现在SQL语句中。这样可以方便后续的维护和修改,减少出错的可能性。
- 兼容性:#占位符语法是Mybatis的特有语法,相对于$占位符语法更具有兼容性。如果项目需要切换到其他ORM框架,使用#占位符语法可以减少代码的修改量。
综上所述,#占位符语法具有防止SQL注入攻击、参数值类型处理、可读性和可维护性、兼容性等优点。因此,在项目中使用动态SQL时,推荐使用#占位符语法。
2.2.$使用
2.2.1. $占位符语法
- $占位符会直接将传入的参数值替换到SQL语句中,不进行预编译处理。
- 使用$占位符时,Mybatis会将参数值直接替换到SQL语句中,生成最终的SQL语句。
- $占位符在生成SQL语句时不会对参数值进行类型处理,参数值会直接拼接到SQL语句中,可能存在安全风险。
- $占位符可以实现动态拼接SQL片段,但是可能存在SQL注入攻击的风险。
2.2.2.$优点
$占位符语法在使用动态SQL时具有以下优点:
- 动态拼接SQL片段:$占位符语法允许在SQL语句中直接使用参数值,可以方便地进行动态拼接SQL片段。这样可以在某些特殊情况下,更灵活地构建SQL语句。
- 字段名动态替换:$占位符语法可以用于动态替换字段名。这在某些场景下非常有用,比如需要根据用户的选择动态查询不同的字段。
- SQL语句灵活性:$占位符语法允许在SQL语句中使用任意有效的SQL表达式。这样可以在SQL语句中进行一些复杂的计算、字符串拼接等操作。
综上所述,$占位符语法在某些特殊情况下具有灵活性和动态拼接SQL的优势,但需要注意SQL注入攻击的风险,并且可能会降低SQL语句的可读性和可维护性。因此,在使用动态SQL时,#占位符语法具有防止SQL注入攻击、参数值类型处理、可读性和可维护性、兼容性等优点。因此,在项目中使用动态SQL时,推荐使用#占位符语法。只有在特殊情况下,需要动态拼接SQL片段或动态替换字段名时,才考虑使用占位符语法,并且需要确保参数值的安全性。
2.3.案例演示
下面是一个案例演示,展示了$符号和#符号的区别:
<select id="getBooksByPrice" parameterType="Map" resultMap="BaseResultMap"> SELECT * FROM books WHERE price > ${minPrice} AND price < #{maxPrice} </select>
在这个示例中,getBooksByPrice是一个查询操作,根据传入的minPrice和maxPrice参数来查询价格在指定范围内的书籍。
- 如果使用$符号占位符,例如传入minPrice=10和maxPrice=20,生成的SQL语句为:
SELECT * FROM books WHERE price > 10 AND price < 20
注意,$符号占位符直接将参数的值替换到SQL语句中,不进行预编译和参数类型处理。
- 如果使用#符号占位符,例如传入minPrice=10和maxPrice=20,生成的SQL语句为:
SELECT * FROM books WHERE price > ? AND price < ?
注意,#符号占位符将参数值作为预编译参数传递给数据库,可以防止SQL注入攻击。
小提示:表名作为变量时,必须使用 ${ }
这是因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 '',这会导致 sql 语法错误,例如:
select * from #{tableName} where name = #{name};
预编译之后的sql 变为:
select * from ? where name = ?;
假设我们传入的参数为 tableName = "books" , name = "ycxw",那么在占位符进行变量替换后,sql 语句变为:
select * from 'books' where name='junlinyi';
上述 sql 语句是存在语法错误的,表名不能加单引号 ''(注意: 反引号 ``是可以的)
三.resultMap和resultType的区别
区别:
- resultType用于简单的结果映射,适用于查询结果列与Java对象属性名一致的情况,自动进行映射。
- resultMap用于复杂的结果映射,适用于查询结果列与Java对象属性名不一致或需要自定义映射关系的情况,需要手动定义映射关系。
esultType适用于简单的结果映射,可以自动进行映射;
resultMap适用于复杂的结果映射,需要手动定义映射关系。
根据具体的需求和情况,选择合适的配置项来实现查询结果的映射。