1. #{}和${}的区别
首先分析这两者的执行结果
#{}的执行结果: [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ? [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String) [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <== Total: 2 ${}的执行结果: [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters:c
从执行结果上可以看出:
#{}首先是使用一个?当做占位符拼接到sql语句,然后再注入值,加上单引号
而${}符号却是直接将数据拼接到原本的语句上面执行,不加单引号
#{}和${}的区别: #{}: 底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值。可以避免SQL注入的风险。
${}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险。 优先使用#{},这是原则。避免SQL注入的风险。
从上诉结果来看,#{}更加安全,所以我们一般使用#{}
什么时候需要用到${}符号呢
如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。
这里举几个例子:
- 比如需要按照传递的值为字段进行数据库的查询,那么就需要将数据库原本的字段使用${}
- 在sql语句中拼接表的名字,需要${}
- 使用批量删除的时候delete from t_car where id in(1,2,3);当这个123是同一个参数,那就必须使用${},delete from t_car where id in(${ids});
模糊查询:like
select * from t_car where brand like '%奔驰%';
在mybatis中有三种实现方式
- 第一种:直接使用${}符号'%${brand}%'
- 第二种:使用专门的一个concat函数:concat('%',#{brand},'%')
- 第三种:使用#{}的方式"%"#{brand}"%"
这两种方式在数据库中表现出来的方式就是#{}使用?占位符然后变成'value'
${}会直接不带''拼接。所以如果直接使用'%#{brand}%'那个占位符就会被当成一个字符串导致错误
所以想要使用#{}就必须避开这一点,要么使用函数,要么第四种。
2.关于MyBatis中别名机制:
可以在核心配置文件中给pojo类声明别名
<typeAliases> <!--别名自己指定的--> <typeAlias type="com.powernode.mybatis.pojo.Car" alias="aaa"/> <typeAlias type="com.powernode.mybatis.pojo.Log" alias="bbb"/> <!--采用默认的别名机制--> <typeAlias type="com.powernode.mybatis.pojo.Car"/> <typeAlias type="com.powernode.mybatis.pojo.Log"/> <!--包下所有的类自动起别名。使用简名作为别名。--> <package name="com.powernode.mybatis.pojo"/> </typeAliases>
在sql标签的输入或者输出是pojo类的时候可以更改为别名
namespace不能使用别名机制。
可以在mapper.xml文件中定义一个语句
<sql id="sql"> name,age,sex </sql>
然后再查询语句的时候直接引入
insert <include refid="sql">
3.mybatis-config.xml文件中的mappers标签。
<mapper resource="CarMapper.xml"/> 要求类的根路径下必须有:CarMapper.xml <mapper url="file:///d:/CarMapper.xml"/> 要求在d:/下有CarMapper.xml文件 <mapper class="全限定接口名,带有包名"/>
mapper标签的属性可以有三个:
resource:这种方式是从类的根路径下开始查找资源。采用这种方式的话,你的配置文件需要放到类路径当中才行。
url: 这种方式是一种绝对路径的方式,这种方式不要求配置文件必须放到类路径当中,哪里都行,只要提供一个绝对路径就行。这种方式使用极少,因为移植性太差。
class: 这个位置提供的是mapper接口的全限定接口名,必须带有包名的。
思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?
<mapper class="com.powernode.mybatis.mapper.CarMapper"/> 如果你class指定是:com.powernode.mybatis.mapper.CarMapper 那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。 注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。 CarMapper接口-> CarMapper.xml LogMapper接口-> LogMapper.xml
现在知道使用class必须在接口的同级或者子包下有同名的xml文件。这个时候有一个隐藏的知识点
这四个文件是在同一个目录下的,使用calss和package的时候可以访问的到
maven在运行的时候会将项目打包成一个target,服务器都是在这里面取数据的,这时候我们打开这个目录
发现是同一个目录。所以我们可以在resources目录下直接创建一个相同的包,在创建目录的时候必须注意的一点
在IDEA的resources目录下新建多重目录的话,必须是这样创建: com/powernode/mybatis/mapper 不能这样: com.powernode.mybatis.mapperm
4.mybatis的传递参数问题
单个参数传递,基本数据类型的直接传递即可(包括八种基本数据类型和八种包装类直接传递即可,包括日期类型业数据这个基本数据)
<select id="selectById" resultType="Student" parameterType="long">
select * from t_student where id = #{id}
</select>
<select id="selectByName" resultType="student">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
<select id="selectByBirth" resultType="student">
select * from t_student where birth = #{birth}
</select>
<select id="selectBySex" resultType="student">
select * from t_student where sex = #{sex}
</select>
其中传递的时候可以不用写parameterType属性,因为mybatis可以自动推断参数类型(指的是基本的)但是你指定之后运行效率更高,比如第二条就是,制定了数据类型和jdbc的类型,这样会提高运行速度。
多个参数传递的情况
首先可以使用map来封装多个参数,用法就是使用#{key},代表的就是value的值
<insert id="insertStudentByMap">
insert into t_student(id,name,age,sex,birth,height) values(null,#{姓名},#{年龄},#{性别},#{生日},#{身高})
</insert>
使用pojo类传值,使用字段来传值
<insert id="insertStudentByPOJO">
insert into t_student(id,name,age,sex,birth,height) values(null,#{name},#{age},#{sex},#{birth},#{height})
</insert>
不使用封装数据的形式直接传递多个参数
<select id="selectByNameAndSex" resultType="Student">
<!--select * from t_student where name = #{arg0} and sex = #{arg1}-->
<!--select * from t_student where name = #{param1} and sex = #{param2}-->
select * from t_student where name = #{arg0} and sex = #{param2}
</select>
一般就会使用这种形式,并且和mybatis的版本有关系
低版本的时候使用的是#{0}#{1}这种,高版本之后才会使用#{arg0}#{arg1}#{param0}#{param1}
mybatis中是怎么实现这种传参方式的呢,其实是将arg0和所代表的数据封住成一个map来传递参数,所以本质上还是map的传参方式。
/**
* 这是多参数。
* 根据name和sex查询Student信息。
* 如果是多个参数的话,mybatis框架底层是怎么做的呢?
* mybatis框架会自动创建一个Map集合。并且Map集合是以这种方式存储参数的:
* map.put("arg0", name);
* map.put("arg1", sex);
* map.put("param1", name);
* map.put("param2", sex);
*
* @param name
* @param sex
* @return
*/
@Param注解
这个注解的作用就是指定map集合中的key。 用法:
List<Student> selectByNameAndSex2(@Param("name") String name, @Param("sex") Character sex);
<select id="selectByNameAndSex2" resultType="Student">
<!--使用了@Param注解之后,arg0和arg1失效了-->
<!--select * from t_student where name = #{arg0} and sex = #{arg1}-->
<!--使用了@Param注解之后,param1和param2还可以用-->
<!--select * from t_student where name = #{param1} and sex = #{param2}-->
select * from t_student where name = #{name} and sex = #{sex}
</select>
本质上其实就是替换了mybatis中map的key值。
5.mybatis的接收参数问题
直接使用实体类接收
Car selectById(Long id);
<select id="selectById" resultType="car"> select <include refid="carColumnNameSql"/> from t_car where id = #{id} </select>
这样直接使用数据库查询的时候会导致类中字段名和数据库字段名不一致的时候无法接收到数据
那么可以使用取别名的方式来查询数据库的数据,这样就可以接收参数了
由于每一次取别名太繁琐,所以可以使用sql标签来定义然后引入就可以了
<!--声明一个SQL片段--> <sql id="carColumnNameSql"> id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType </sql>
值得注意的是,查询单个pojo类和所有pojo类使用的resultType都指向类,因为mybatis可以自动识别数据是否是list,直接指定其中的泛型就可以。所以不确定数据库中有几条记录的时候可以直接使用list当做返回类型,返回一条也可以使用list。
使用map来接收数据库数据
查询单条数据的时候会直接这样返回
/**
* 根据id获取汽车信息。将汽车信息放到Map集合中。
* +-----+---------+----------+-------------+--------------+----------+
* | id | car_num | brand | guide_price | produce_time | car_type |
* +-----+---------+----------+-------------+--------------+----------+
* | 158 | 1111 | 比亚迪汉 | 3.00 | 2000-10-10 | 新能源 |
* +-----+---------+----------+-------------+--------------+----------+
*
* Map<String, Object>
* k v
* -----------------------
* "id" 158
* "car_num" 1111
* "brand" 比亚迪汉
* ....
*
* @param id
* @return
*/
Map<String, Object> selectByIdRetMap(Long id);
查询多条语句的时候就会返回一个List<Map<String,Object>> selectAllRetListMap();
这样会显得数据十分臃肿并且不好使用
因此我们可以改造一下返回方式
@MapKey("id")
使用这个注解可以在查询数据的时候返回他的value值来赋值给map的key
/**
* 查询所有的Car,返回一个大Map集合。
* Map集合的key是每条记录的主键值。
* Map集合的value是每条记录。
* {
* 160={car_num=3333, id=160, guide_price=32.00, produce_time=2000-10-10, brand=奔驰E300L, car_type=新能源},
* 161={car_num=4444, id=161, guide_price=32.00, produce_time=2000-10-10, brand=奔驰C200, car_type=新能源},
* 162={car_num=9999, id=162, guide_price=30.00, produce_time=2020-10-11, brand=帕萨特, car_type=燃油车},
* 163={car_num=9991, id=163, guide_price=30.00, produce_time=2020-11-11, brand=凯美瑞, car_type=燃油车},
* 158={car_num=1111, id=158, guide_price=3.00, produce_time=2000-10-10, brand=比亚迪汉, car_type=新能源},
* 159={car_num=2222, id=159, guide_price=32.00, produce_time=2000-10-10, brand=比亚迪秦, car_type=新能源}
* }
* @return
*/
@MapKey("id") // 将查询结果的id值作为整个大Map集合的key。
Map<Long, Map<String,Object>> selectAllRetMap();
在上述查询的时候,需要给数据字段取别名,这样也是十分麻烦,所以可以使用一个结果映射
resultMap标签
<!--
1.专门定义一个结果映射,在这个结果映射当中指定数据库表的字段名和Java类的属性名的对应关系。
2. type属性:用来指定POJO类的类名。
3. id属性:指定resultMap的唯一标识。这个id将来要在select标签中使用。
-->
<resultMap id="carResultMap" type="Car">
<!--如果数据库表中有主键,一般都是有主键,要不然不符合数据库设计第一范式。-->
<!--如果有主键,建议这里配置一个id标签,注意:这不是必须的。但是官方的解释是什么呢?这样的配置可以让mybatis提高效率。-->
<id property="id" column="id"/>
<!--<result property="id" column="id"/>-->
<!--property后面填写POJO类的属性名-->
<!--column后面填写数据库表的字段名-->
<result property="carNum" column="car_num" javaType="java.lang.String" jdbcType="VARCHAR"/>
<!--如果column和property是一样的,这个可以省略。-->
<!--<result property="brand" column="brand"/>-->
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type" javaType="string" jdbcType="VARCHAR"/>
</resultMap>
这样在填写sql中返回值的时候可以直接使用这个
<select id="selectAllByResultMap" resultMap="carResultMap">
select * from t_car
</select>
驼峰映射
上述的resultMap方式是用来自定义映射规则的,你的实体类字段可以和数据库名称不一致
驼峰映射是一种自动的 resultMap映射方式,但是有一定的规定需要准守
java中的取名必须准守guidePrice这种驼峰式,而数据库对应的字段必须使用guide_price这种下划线的形式,不然无法生效,所以说虽然变得更加方便,但是需要准守他的规定。
使用方式:在核心配置文件上加上这个
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
6.动态sql
if标签
if标签一般用于动态判断,但是不能自动去除多余的and标签
<select id="selectByMultiCondition" resultType="Car">
select * from t_car where 1 = 1
<!--
1. if标签中test属性是必须的。
2. if标签中test属性的值是false或者true。
3. 如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。
4. test属性中可以使用的是:
当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param("brand"),那么这里只能使用brand
当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2....
当使用了POJO,那么test中出现的是POJO类的属性名。
5. 在mybatis的动态SQL当中,不能使用&&,只能使用and。
-->
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</select>
where标签
where标签可以帮助我们去除前面多余的and或者or标签。
<select id="selectByMultiConditionWithWhere" resultType="Car">
select * from t_car
<!--where标签是专门负责where子句动态生成的。-->
<where>
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</where>
</select>
trim标签
这个标签可以动态的处理里面的数据,可以在前后加上或者去掉某些属性,如果里面为空则不生效。
<select id="selectByMultiConditionWithTrim" resultType="Car">
select * from t_car
<!--
prefix:加前缀
suffix:加后缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀
-->
<!--prefix="where" 是在trim标签所有内容的前面添加 where-->
<!--suffixOverrides="and|or" 把trim标签中内容的后缀and或or去掉-->
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" or
</if>
<if test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice} and
</if>
<if test="carType != null and carType != ''">
car_type = #{carType}
</if>
</trim>
</select>
set标签
用于动态的修改值,不使用set标签的时候只能一次性更改所有的值。set标签和if一起就可以实现动态更改,set标签的作用是去除多余的,
<update id="updateBySet">
update t_car
<set>
<if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
<if test="brand != null and brand != ''">brand = #{brand},</if>
<if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
<if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
<if test="carType != null and carType != ''">car_type = #{carType},</if>
</set>
where
id = #{id}
</update>
choose标签
choose标签的作用就相当于switch,只能选择一个条件执行,符合when的条件就执行,不执行下面的其他条件了。
<select id="selectByChoose" resultType="Car">
select * from t_car
<where>
<choose>
<when test="brand != null and brand != ''">
brand like "%"#{brand}"%"
</when>
<when test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice}
</when>
<otherwise>
car_type = #{carType}
</otherwise>
</choose>
</where>
</select>
foreach标签
使用foreach批量删除
<delete id="deleteByIds">
<!--
delete from t_car where id in(
<foreach collection="ids" item="aaaaaaa" separator=",">
#{aaaaaaa}
</foreach>
)
-->
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
foreach中的属性详解
<!--
foreach标签的属性:
collection:指定数组或者集合
item:代表数组或集合中的元素
separator:循环之间的分隔符
open: foreach循环拼接的所有sql语句的最前面以什么开始。
close: foreach循环拼接的所有sql语句的最后面以什么结束。
collection="ids" 第一次写这个的时候报错了,错误信息是:[array, arg0]
什么意思?
map.put("array", 数组);
map.put("arg0", 数组);
-->
这里要注意的是collection的属性名不能随便写,要么通过@param指定,要么写array或者arg0
使用foreach批量增加
<insert id="insertBatch">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
</foreach>
</insert>