1 #和$的区别
#{}表示一个占位符号
- 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
- #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。
- 可以自动对值添加 ’ ’ 单引号
${}表示拼接 sql 串
- 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换,
- ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
- 比如order by id 这种的,以id排序 那么这个id 是没有单引号的,就是简单的SQL拼接,所以我们应该使用${} 而不是#{}
多个参数
当我涉及到多个参数传参的时候,这个时候,我们直接使用变量名会发现控制台有错误提示
Parameter 'XXX' not found. Available parameters are [arg1, arg0, param1, param2]
这一行报错的意思是XXX变量没有生命,我们可以用的变量名为arg1,arg0,param1,param2
当我们有多个变量的时候,我们就需要通过arg0,arg1来获取对应的变量了
// 接口方法 int updateNickname( int id,String nickname);
<update id="updateNickname"> # 读取第二个变量nickname和第一个变量id update t_user set nickname = #{arg1} where id = #{arg0} </update>
这种方式不是特别直观,当我们调整顺序之后,相应的xml里的顺序也需要调整,故我们一般会通过第二种方式来解决这个问题。通过注解的方式
// 接口方法 int updateNickname( @Param("id") int id,@Param("nickname")String nickname);
<update id="updateNickname"> update t_user set nickname = #{nickname} where id = #{id} </update>
包装类型单个参数
java有八个基本数据类型,我们在使用这些基本数据类型,可以直接取到对应的value,比如我们上次课程中涉及到的int类型。
但是当我们的参数是包装类型时,比如String或者是User的实体类,这时mybatis就或读取包装类型中的属性,比如我们在使用User实体类时,直接使用实体类中的#{id}属性。但是String类型并没有对应的属性,我们希望的是直接获取String中的值,那么我们在取值的时候,就会出现这个错误。
There is no getter for property named 'xxx' in 'class java.lang.String'
这个报错的意思就是从String类型中获取不到xxx属性
这个使用我们也需要通过@Param
的注解来解决这个问题
// 接口方法 List<User> selectList(@Param("orderBy") String orderBy);
<select id="selectList" parameterType="String" resultType="User"> select * from t_user ${orderBy} </select>
小结:
一般我们获取变量值的时候使用#{}来读取属性,如果需要进行sql拼接的时候可以使用${},使用${}的时候要注意防止sql注入。
2 paramerterType 和 resultType
2.1 paramerterType
我们在上一章节中已经介绍了 SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以
是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。
基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式。在这里,之所以我们可以直接写类名的原因就是因为这些这些常用类型,mybatis已经帮我们配置好了别名。下图是类型对照说明。
针对于实体类,我们如果也想用简写,就需要我们自己去配置别名了。
2.2 resultType
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
我们在前面的 CRUD 案例中已经对此属性进行过应用了。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须
使用全限定类名。
同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
4.3 resultMap
我们在上一节,提到在声明返回值类型为实体类型之后,实体中的属性必须和查询语句中的属性对应上,但是我们在开发的过程中也难免会遇到无法对应的情况。比如说我们在进行数据库设计的时候,多个单词往往是用_连接,但是在实体类中的属性往往采用小驼峰的方式命名。这就导致字段名无法对应上,这个时候我们就需要配置resultMap来解决这个问题了。
通过resultMap,我们可以指定查询结果字段和实体属性字段的映射关系。
<resultMap id="userResult" type="User"> <id column="id" property="id" /> <result property="nickname" column="nickname" /> <result property="schoolName" column="school_name" /> </resultMap>
3 mybatis-config.xml 配置文件
3.1 属性配置的两种方式
3.1.1 直接配置属性
<properties> <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="jdbc.url" value="jdbc:mysql://localhost:3306/test"/> <property name="jdbc.username" value="root"/> <property name="jdbc.password" value="root"/> </properties>
3.1.2 读取配置文件
创建db.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/erp16?useUnicode=true&characterEncoding=UTF-8 username=root password=root
配置文件读取
<properties resource="db.properties" />
3.2 typeAliases属性
<typeAliases> <!-- 单个别名定义 --> <typeAlias alias="user" type="com.tledu.zrz.pojo.User"/> <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> <package name="com.tledu.zrz.pojo"/> <package name="其它包"/> </typeAliases>
3.3 mapper属性
Mappers是我们所说的映射器,用于通过mybatis配置文件去找到对应的mapper文件,关于这个属性有三种用法。
3.3.1 Resource
使用相对于类路径的资源如:<mapper resource="com/tledu/zrz/dao/IUserDao.xml" />
3.3.2 class
使用 mapper 接口类路径
如:<mapper class="com.tledu.zrz.dao.UserDao"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
3.3.3 package
注册指定包下的所有 mapper 接口
如:<package name="com.tledu.zrz.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中,并且这里如果希望能够扫描到包下面的xml文件的话,需要在maven中进行配置。
4 动态sql
动态SQL是MyBatis的一个强大特性之一,如果你有使用 JDBC 或其他 相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空 格或在列表的最后省略逗号。动态 SQL 可以彻底处理这种痛苦。
动态 SQL 元素和使用 JSTL 或其他相似的基于 XML 的文本处理器相似。
4.1 if
<select id="list" parameterType="User" resultMap="userResult"> select * from t_user where 1=1 <if test="username != null and username != ''"> and username = #{username} </if> <if test="nickname != null and nickname != ''"> and nickname like concat('%',#{nickname},'%') </if> </select>
4.2 choose、when、otherwise
类似于Java中的switch case default
<select id="list" parameterType="User" resultMap="userResult"> select * from t_user where 1=1 <choose> <when test="id != null"> and id = #{id} </when> <when test="username != null and username != ''"> and username = #{username} </when> <otherwise> and nickname = #{nickname} </otherwise> </choose> </select>
4.3 where
在我们where条件不确定的时候,我们每次都需要加上一个1=1才能保证后面的拼接不会出现错误,使用where标签后,我们就不需要考虑拼接的问题了,直接在里面添加条件即可。通过where标签也可以让代码更具有语义化,方便维护代码
<select id="list" parameterType="User" resultMap="userResult"> select * from t_user where <where> <if test="username != null and username != ''"> and username = #{username} </if> <if test="nickname != null and nickname != ''"> and nickname like concat('%',#{nickname},'%') </if> </where> </select>
4.4 set
在进行更新操作的时候,也可以用set标签添加更新条件,同样我们也就不需要在进行字符串拼接了,set标签会帮我们进行拼接。
<update id="updateNickname"> update t_user <set> <if test="nickname != null and nickname != ''"> nickname = #{nickname}, </if> <if test="username != null and username != ''"> username = #{username}, </if> </set> where id = #{id} </update>
4.4 foreach
我们在开发项目中难免需要进行批量操作,这个时候就可以使用foreach进行循环添加。
例如批量插入
<insert id="batchInsert"> insert into t_user (username, password, nickname) VALUES <foreach collection="list" index="idx" item="item" separator=","> (#{item.username},#{item.password},#{item.nickname}) </foreach> </insert>
in 查询
<select id="list" parameterType="User" resultMap="userResult"> select * from t_user <where> <if test="user.username != null and user.username != ''"> and username = #{user.username} </if> <if test="user.nickname != null and user.nickname != ''"> and nickname like concat('%',#{user.nickname},'%') </if> and id in <foreach collection="idList" item="item" separator="," open="(" close=")"> #{item} </foreach> </where> </select>
属性说明
- collection 需要遍历的列表
- item 每一项的形参名
- index 每一项索引名
- separtor 分隔符
- open 开始符号
- close 关闭符号
5 联查
在项目中,某些实体类之间肯定有关联关系,比如一对一,一对多等,在mybatis 中可以通过association和collection,来处理这些关联关系。
5.1 1对1
在实现1对1映射的时候,可以通过association属性进行设置。在这里有三种方式
在地址表中,每个地址对应有一个创建用户,每次查询地址的时候希望查询到创建用户的内容
5.1.1 使用select
<resultMap id="address" type="Address"> <id column="id" property="id" /> <association property="user" column="user_id" javaType="User" select="com.tledu.erp.dao.IUser2Dao.selectById"/> </resultMap>
- property配置了实体类对应的属性
- column配置了关联字段
- select对应了IUser2Dao中的查询语句
在执行sql的过程中就会同时调用在IUserDao中定义的sql进行联查。
这种方式会执行两次sql语句,效率相对较低,同时还需要先在IUserDao中进行定义后才能使用,比较麻烦
5.1.2 直接进行联查,在association中配置映射字段
这里可以直接写联查,需要转换的字段可以在association中进行配置。
<resultMap id="address" type="Address" autoMapping="true"> <id column="id" property="id" /> <association property="user" column="user_id" javaType="User" > <id column="user_id" property="id" /> <result column="school_name" property="schoolName" /> </association> </resultMap> <select id="selectOne" resultMap="address"> select * from t_address left join t_user tu on tu.id = t_address.user_id where t_address.id = #{id} </select>
autoType代表自动封装,如果不填写,则需要添加所有的对应关系。
这种方式的问题是,当association需要被多次引用的时候,就需要进行多次重复的配置,所以我们还有第三种方式,引用resultMap。
5.1.3 嵌套的resultType
<resultMap id="addressMap" type="Address" autoMapping="true"> <id column="id" property="id"/> <association property="user" column="user_id" resultMap="userMap"> </association> </resultMap> <resultMap id="userMap" type="User" autoMapping="true"> <id column="user_id" property="id" /> <result column="school_name" property="schoolName"/> </resultMap> <select id="selectOne" resultMap="addressMap"> select t_address.id, addr, phone, postcode, user_id, username, password, nickname, age, sex, school_name from t_address left join t_user tu on tu.id = t_address.user_id where t_address.id = #{id} </select>
通过这种方式,userMap就可以被多次引用了。
- 通过别名保证查询的每一个元素是唯一的,以防止出现错乱的情况
- mybatis官网提醒,需要设置id提高查询性能
5.1 1对多
对于address来说,一个地址对应一个创建用户,但是对于User来说,一个用户可能对应创建了多条地址信息,这种情况,在mybatis中就可以通过collections解决。
同样也是有三种方法
5.2.1 使用select
这里的用法和1对1的时候一致
在AddressDao中添加
<select id="selectByUserId" resultType="Address"> select * from t_address where user_id = #{userId} </select>
在UserDao中添加
<resultMap id="userResult" type="User" autoMapping="true"> <id column="id" property="id"/> <result property="nickname" column="nickname"/> <result property="schoolName" column="school_name"/> <collection property="addressList" column="id" autoMapping="true" select="com.tledu.erp.dao.IAddressDao.selectByUserId" > </collection> </resultMap> <select id="selectById" parameterType="int" resultMap="userResult"> select * from t_user where id = #{id} </select>
5.2.2 直接进行联查,在collection中配置映射字段
这里可以直接写联查,需要转换的字段可以在collection中进行配置。
<resultMap id="userResult" type="User" autoMapping="true"> <id column="id" property="id"/> <result property="nickname" column="nickname"/> <result property="schoolName" column="school_name"/> <collection property="addressList" column="phone" ofType="Address" autoMapping="true"> <id column="address_id" property="id" /> </collection> </resultMap> <select id="selectById" parameterType="int" resultMap="userResult"> select tu.id, username, password, nickname, age, sex, school_name, ta.id as address_id, addr, phone, postcode, user_id from t_user tu left join t_address ta on tu.id = ta.user_id where tu.id = #{id} </select>
autoMapping代表自动封装,如果不填写,则需要添加所有的对应关系。
这里需要配置ofType来指定返回值类型
这种方式的问题是,当collection需要被多次引用的时候,就需要进行多次重复的配置,所以我们还有第三种方式,引用resultMap。
5.1.3 嵌套的resultType
<resultMap id="userResult" type="User" autoMapping="true"> <!-- <id column="id" property="id"/>--> <result property="nickname" column="nickname"/> <result property="schoolName" column="school_name"/> <collection property="addressList" column="phone" ofType="Address" resultMap="addressResultMap" autoMapping="true"> </collection> </resultMap> <resultMap id="addressResultMap" type="Address" autoMapping="true"> <id column="address_id" property="id" /> </resultMap> <select id="selectById" parameterType="int" resultMap="userResult"> select tu.id, username, password, nickname, age, sex, school_name, ta.id as address_id, addr, phone, postcode, user_id from t_user tu left join t_address ta on tu.id = ta.user_id where tu.id = #{id} </select>
通过这种方式,userMap就可以被多次引用了。