Mybatis
1. 介绍
Mybatis是一个ORM框架。
ORM:Object RelationShip Mapping 对象关系映射
其实Mybatis就是把数据库中的记录映射为Java对象,把Java对象映射为数据库中的记录 的这么一个框架。
2. Mybatis入门案例
-
导包
<!-- myabtis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- 数据库的驱动包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
-
配置
-
配置一个Mybatis的主配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 环境配置,其实也就是数据库连接的配置--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/33th?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- 映射器的配置--> <mappers> <!-- 这个mapper配置文件是用来干嘛的呢?其实是用来放SQL语句的 --> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> </mappers> </configuration>
-
配置映射器 ( Mapper)
<?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"> <!-- 我们后续所有的关于SQL语句的配置都需要写在这个mapper标签下 namespace:命名空间 select: 表示这里面需要放一个查询语句 id: 是你这个sql语句的id值,在一个Mapper.xml文件里面,这个id不能重复 resultType: 结果类型 parameterType: 参数类型 --> <mapper namespace="cskaoyan"> <select id="selectAccountById" resultType="com.cskaoyan.Account" parameterType="int"> select * from account where id = #{id} </select> </mapper>
-
-
使用
public class MybatisMain { static SqlSession sqlSession; static { // 构建一个SqlSessionFactory // 获取主配置文件流 InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream("mybatis-config.xml"); } catch (IOException e) { e.printStackTrace(); } // InputStream resource = MybatisMain.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); // 获取SqlSessionFactory 解析我们的 Mybatis-config.xml,进而解析Mapper.xml 获取到SQL语句,放到内存 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取SqlSession 这个SQLSession可以帮助我们执行SQL语句 sqlSession = sqlSessionFactory.openSession(); } public static void main(String[] args){ // 执行SQL语句 // 参数一:SQL语句的坐标 // 参数二:传入的参数 Account account = sqlSession.selectOne("cskaoyan.selectAccountById", 1); System.out.println("account:" + account); } }
类里面为何定义成员变量的时候,不用int, 用integer?
主要是为了和框架适配,避免出错
而且比如在数据库插入过程中自增的可以插入null,但是如果用int类型就不能插入,用Integet就可以
3. Mybatis的动态代理
Mybatis的动态代理其实就是可以帮助我们去动态的获取接口的实现类
@Test
public void testSelectAccountById(){
// 获取到AccountMapper的代理对象
AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
// 调用代理对象里面的方法
Account account = accountMapper.selectAccountById(1);
// 接口的全限定名称+ 方法的名称
// 调用这个接口,实际上是需要调用
//Account account = sqlSession.selectOne("cskaoyan.selectAccountById", id);
System.out.println("account:" + account);
}
使用动态代理相关的条件:
- Mapper.xml和mapper.java 在编译之后,要在同一级目录下(规范)
- mapper.xml的namespace必须是mapper.java 的全限定名称
- mapper.xml里面 的id值和方法名对应
- mapper.xml的resultType和parameterType必须和方法的参数以及返回值对应
4. 配置
4.1 properties
这个配置是可以帮助我们引入properties配置文件,一般是用来引入数据源的配置
引入properties配置
取值:
4.2 settings
这个里面可以配置很多内容,大多数都是和Mybatis的性能有关的,例如缓存,懒加载等等
<!-- 这个里面配置的是Mybatis极为重要的一些配置,例如缓存,懒加载,日志等等-->
<settings>
<!-- 配置日志的标准输出 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4.3 typeAliases
这个里面是关于别名的配置的
-
配置方式一:
<!-- 类型别名 :我们可以给一些对象起别名 --> <typeAliases> <!-- 给对象起别名 1. 好处其实就是可以去简化我们的代码 2. 不足是我们mapper.xml的可读性会变差 --> <typeAlias type="com.cskaoyan.vo.Account" alias="account"/> </typeAliases>
-
配置方式二:
<typeAliases> <!-- 起别名的第二种方式 --> <package name="com.cskaoyan.vo"/> </typeAliases>
4.4 environments
<!-- 环境配置,其实也就是数据库连接的配置-->
<environments default="development">
<!--
1. 开发环境 development 对应你的电脑
2. 测试环境 test 测试服务器(其实就是把你的应用程序在一个测试服务器(Linux)上运行起来)
3. BETA环境 beta 基本上和生产环境没什么区别了(数据库和生产环境是同一个),有很多公司没有这一步
4. 生产环境 prod 正式环境(其实就是把你的应用程序在一个正式服务器上运行起来)
大多数情况下,不同的环境要有不同的数据库的配置
软件公司的成本:
硬件成本 租-阿里云、腾讯云
人力成本
-->
<environment id="development">
<!--
JDBC:通过数据库提供的connection来管理事务
MANAGED:依赖于让容器来管理事务
容器:Tomcat | Spring
-->
<transactionManager type="JDBC"/>
<!--
type:
UNPOOLED 不使用数据库连接池,每一次访问数据库都会新建一个连接,使用完之后就关闭
POOLED 使用数据库连接池,会新建一个连接池,每一次使用连接就是从池子里面获取连接
JNDI:使用外部的数据源
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!--<environment id="prod">-->
<!--<transactionManager type="JDBC"/>-->
<!--<dataSource type="POOLED">-->
<!--<property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/33th?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="123456"/>-->
<!--</dataSource>-->
<!--</environment>-->
</environments>
4.5 mapper
这个是一个很重要的配置,可以帮助我们去找到对应的mapper配置文件
<!-- 映射器的配置-->
<mappers>
<!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--
第一种:直接指定文件的位置 推荐
-->
<mapper resource="com/cskaoyan/mapper/AccountMapper.xml"/>
<!--<mapper resource="com/cskaoyan/mapper/AccountMapper2.xml"/>-->
<!--
第二种:表示注册这个包下面的所有的mapper.xml文件
-->
<!--<package name="com.cskaoyan.mapper"/>-->
</mappers>
5. 输入映射
输入映射是什么意思呢?其实输入映射就是说Mybatis是如何传值的
5.1 一个简单参数
什么叫简单参数呢?其实就是基本类型和String
5.2 传入多个参数
mapper
// 传入多个简单参数
// 根据语文成绩区间查询学生信息
List<Student> selectStudentListByChinese(
// 需要告诉Mybatis传入的参数的名字是什么
@Param("minScore") Integer minScore,
@Param("maxScore") Integer maxScore);
mapper.xml
<!-- parameterType 可以省略 resultType不能省略-->
<select id="selectStudentListByChinese" resultType="com.cskaoyan.vo.Student">
select id,name,english,math,chinese,birthday,native_place as nativePlace
from student
where chinese between #{minScore} and #{maxScore}
</select>
我们在做增删改操作的时候,需要在执行完SQL语句之后,执行一句
sqlSession.commit();
5.3 传入对象
不加@Param注解
mapper
// 传入对象
// insert into student values (?,?,?,?,?,?)
int insertStudent(Student student);
mapper.xml
<!--插入 返回的int-->
<!-- #{ 成员变量的名字 }-->
<insert id="insertStudent">
insert into student values (#{id},#{name},#{chinese},#{english},#{math},#{birthday},#{nativePlace})
</insert>
加上@Param注解
mapper
// 传入对象
// insert into student values (?,?,?,?,?,?)
int insertStudent2(@Param("student") Student student);
mapper.xml
<!--插入 返回的int-->
<!-- 通过 #{ 参数的注解.成员变量的名字} 来取值-->
<insert id="insertStudent2">
insert into student values (#{student.id},#{student.name},#{student.chinese},#{student.english},#{student.math},#{student.birthday},#{student.nativePlace})
</insert>
5.4 通过Map来传值
不推荐使用通过map来传值
-
不使用注解
mapper
// 传入map int deleteStudentByNameOrId(Map map);
mapper.xml
<delete id="deleteStudentByNameOrId"> delete from student where id = #{id} or name = #{name} </delete>
#{map里面key} 来取值
-
使用@Param注解
mapper
// 传入map int deleteStudentByNameOrId2(@Param("map") Map map);
mapper.xml
<delete id="deleteStudentByNameOrId2"> delete from student where id = #{map.id} or name = #{map.name} </delete>
5.5 通过位置来传值
不推荐使用位置来传值(因为容易出错)
mapper
// 通过位置来传值
List<Student> selectStudentListByScore(Integer chinese,Float math,Float english);
mapper.xml
<select id="selectStudentListByScore" resultType="com.cskaoyan.vo.Student">
select id,name,english,math,chinese,birthday,native_place as nativePlace from student
where chinese > #{arg0} and math > #{arg1} and english > #{arg2}
</select>
我们通过按照位置来传值的时候,可以这样来取值
-
#{arg0}、#{arg1}、#{arg2} …
-
#{param1} 、 #{param2}、 #{param3} …
5.6 #{}和${} 的区别
面试会问
总结一下:
- ${} 来取值是字符串拼接的方式
- #{}来取值是预编译的方式
我们在以后的工作中,去取值的时候应该尽量使用 #{} 这种方式来取值,以为 字符串拼接的方式会导致 SQL注入的问题
那么我们${}这种取值的方式是不是一无是处呢?不是的
如果在一个SQL语句里面需要我们传入表名,列名的时候,我们需要使用 ${} 这种方式来取值
-
1 取列名
order by、 group by
select * from student order by columnName desc
这种需要我们把列名传入进去mapper
// 传入列名,根据列名来排序,取前三个 List<Student> selectStudentListOrderByColumnNameDescLimit3(@Param("columnName") String columnName);
mapper.xml
<select id="selectStudentListOrderByColumnNameDescLimit3" resultType="com.cskaoyan.vo.Student"> select id,name,english,math,chinese,birthday,native_place as nativePlace from student order by ${columnName} desc limit 3 </select>
我们发现,在使用 ${} 取列名的时候结果是正常的,但是通过#{}这种方式来取值的时候,order by这个关键字会失效。因为#{}是使用的预编译的方式,在预编译的时候,还没有把参数(列名)传递进去,所以不能够根据列名去排序
(我觉得可以理解为:在预编译的时候,需要去检查是否有某个表或者是某个列,所以预编译阶段必须获取到表名还有列名,而不可以在预编译后再传入取值)
-
2 取表名
在我们分表的时候,我们需要传入表名
mapper// 传入表名 List<Student> selectStudentListByTableName(@Param("tableName") String tableName);
mapper.xml
<select id="selectStudentListByTableName" resultType="com.cskaoyan.vo.Student"> select id,name,english,math,chinese,birthday,native_place as nativePlace from ${tableName} </select>
说明:#{} 来取值和 ${} 来取值不冲突,可以共用在同一条SQL语句里面
<select id="selectStudentByIdAndTableName" resultType="com.cskaoyan.vo.Student">
select id,name,english,math,chinese,birthday,native_place as nativePlace from ${tableName}
where id = #{id}
</select>
先查看有没有$,如果有就进行字符串拼接,然后再预编译,插入#的值
6. 输出映射
输出映射其实说的就是我们Mybatis如何封装参数的
如何复制Module
-
复制module文件
-
修改
复制完之后把module改为你想要的名字
修改文件夹的名字以及文件夹下面pom.xml文件里面 artfactId的值,使他们两保持一致
-
删除
删除target文件夹、删除.iml文件,反正只留下src和pom就可以 -
导入module(打开project Structure)
选中pom.xml文件
接下来点击 应用和ok即可
6.1 简单类型
mapper
// 定义了一个方法
String selectNameById(@Param("id") Integer id);
mapper.xml
<select id="selectNameById" resultType="string">
select name from account where id = #{id}
</select>
注意:我们的查询语句,一定要写 resultType,不能不写
6.2 JavaBean
mapper
// 返回对象
Account selectAccountById(@Param("id") Integer id);
// 当我们返回一个对象的集合或者是一个对象的数组的时候,我们只需要在接口的方法的返回值这里声明即可
// mapper.xml里面配置的resultType都是单个Bean的全限定名称
List<Account> selectAccountList();
Account[] selectAccoutArray();
mapper.xml
<select id="selectAccountById" resultType="com.cskaoyan.vo.Account">
select * from account where id = #{id}
</select>
<select id="selectAccountList" resultType="com.cskaoyan.vo.Account">
select * from account
</select>
<select id="selectAccoutArray" resultType="com.cskaoyan.vo.Account">
select * from account
</select>
6.3 ResultMap
这个可以帮我们去做手动的映射。
例如我们之前,假如数据库里面的列名和Java对象里面的成员变量的名字不一致,我们通过别名来解决的这种问题。其实也可以通过resultMap来解决。当然ResultMap的使用,更多的是在多表查询这一块去做复杂映射的。
mapper
// 通过resultMap来做映射
Account2 selectAccountByNameWithResultMap(@Param("name")String name);
mapper.xml
<resultMap id="accountMap" type="com.cskaoyan.vo.Account2">
<!--
column: 表示列名
property: 表示成员变量名
jdbcType: 数据库字段的类型
javaType: 成员变量的类型
jdbcType和javaType可以省略 jdbcType="int" javaType="int"
说明:假如我们成员变量的名字和数据库里面表的列名是一致的时候,并且类型也能对应的时候,那么Mybatis会给我们去做自动映射
// 因为自动映射是隐式的,所以一般我们不建议大家使用自动映射
-->
<!--<result column="id" property="uid" />-->
<!-- 对于主键这一列,可以使用id这个标签 主键这一列使用id标签主要是为了效率考虑-->
<id column="id" property="uid"/>
<result column="name" property="username"/>
<result column="money" property="usermoney"/>
<result column="role" property="userrole"/>
</resultMap>
<select id="selectAccountByNameWithResultMap" resultMap="accountMap">
select id,name,money,role from account where name = #{name}
</select>
7. 插件
7.1 Mybatis的插件
- 支持xml和接口文件来回跳转
- 可以帮助我们自动生成@Param注解
- 可以帮助我们自动生成<update等标签>
7.2 Lombok插件
lombok这个插件其实是一个可以帮助我们自动去生成getter、setter、toString等方法的一个插件
-
导包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
-
在idea中下载插件
因为Idea不能够识别lombok的注解的功能,所以我们需要在idea中下载lombok的插件,来识别lombok注解的功能
-
使用
在JavaBean上面增加注解
@Setter@Getter@ToString@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCode
@Data
这些注解其实是在编译的时候生效的。也就是说,Lombok是在代码编译的时候给我们自动去生成这些方法
我们应该使用Lombok吗?
我们可以确认的是,Lombok可以帮助我们提高开发效率。
使用不使用Lombok还是得看公司里面的同事用不用,假如公司里面都用,那就可以用。假如公司里面没人用,那你就别用
8. 动态SQL
动态SQL值的是Mybatis可以根据一些标签来动态的改变执行的SQL。动态SQL是Mybatis最重要的功能之一,其实就是给我们提供了一个标签库来供我们使用
8.1 where(重要)
where标签可以帮助我们去生成where关键字,但是还有别的功能
8.2 if(重要)
if标签可以帮助我们动态的根据条件拼接SQL语句
mapper
// 按条件查询账户
// 如果传入了金钱数量大于100,那我们查询余额大于100的账户
// 如果传入的金钱数量小于100,那我们查询余额小于100的账户
List<Account> selectAccountListByMoney(@Param("money") Integer money);
mapper.xml
<select id="selectAccountListByMoney" resultType="com.cskaoyan.vo.Account">
select * from account where
<!--
if test="" 引号中是一个OGNL表达式,你可以认为就是一个Boolean表达式,里面的结果是一个true或者是false
在OGNL表达式中,我们大于,小于有特殊的写法
> gt
< lt
>= gte
<= lte
-->
<if test="money gte 100">
money >= 100
</if>
<if test="money lt 100">
money < 100
</if>
</select>
说明:where标签通常和if标签配合起来一起使用
<select id="selectAccountListBySelective" resultType="com.cskaoyan.vo.Account">
select * from account
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null">
or name = #{name}
</if>
<if test="money != null">
or money = #{money}
</if>
<if test="role != null">
or role = #{role}
</if>
</where>
</select>
where标签的作用:
- 可以帮助我们去自动添加一个where关键字
- 当where标签里面没有sql片段的时候,不会帮助我们去拼装一个额外的where关键字
- where标签可以帮助我们去除相邻的 and 或者是 or关键字
8.3 choose when otherwise
就相当于我们Java里面 if … else…
mapper
// 按条件查询账户
// 如果传入了金钱数量大于等于100,那我们查询余额大于等于100的账户
// 如果传入的金钱数量小于100,那我们查询余额小于100的账户
List<Account> selectAccountListByMoneyUseChooseWhen(@Param("money") Integer money);
mapper.xml
<select id="selectAccountListByMoneyUseChooseWhen" resultType="com.cskaoyan.vo.Account">
select * from account
<where>
<choose>
<when test="money gte 100">
money >= 100
</when>
<otherwise>
money < 100
</otherwise>
</choose>
</where>
</select>
8.4 trim
trim这个标签可以帮助我们去修剪SQL语句,也就是可以动态的去增加指定的字符或者是去除指定的字符
<update id="updateAccountBySelective">
update account
<!--
suffixOverrides 去除指定的后缀
prefixOverrides 去除指定的前缀
prefix 增加指定的前缀
suffix 增加指定的后缀
-->
<trim suffixOverrides="," prefix="set">
<if test="account.name != null">
name = #{account.name},
</if>
<if test="account.money != null">
money = #{account.money},
</if>
<if test="account.role != null">
role = #{account.role},
</if>
</trim>
<where>
<if test="account.id != null">
id = #{account.id}
</if>
</where>
</update>
8.5 SET(重要)
这个标签就和 的效果是一样的
<update id="updateAccountBySelectiveWithSet">
update account
<set>
<if test="account.name != null">
name = #{account.name},
</if>
<if test="account.money != null">
money = #{account.money},
</if>
<if test="account.role != null">
role = #{account.role},
</if>
</set>
<where>
<if test="account.id != null">
id = #{account.id}
</if>
</where>
</update>
8.6 sql-include(重要)
我们可以在xml文件中把公共的代码抽离出来,放到sql标签下,然后通过include标签来引入
8.7 foreach(重要)
这个标签是可以帮助我们在sql里面去循环
有两个应用场景
-
in查询
根据一个数组或者是一个集合去查询
mapper
// in 查询 select * from account where id in (1,2,3); List<Account> selectAccountListByIdArray(int[] ids); List<Account> selectAccountListByIdArrayWithParam(@Param("ids") int[] ids); // 通过idList去查询accountList List<Account> selectAccountListByIdList(List<Integer> ids); List<Account> selectAccountListByIdListWithParam(@Param("ids") List<Integer> ids);
mapper.xml
<select id="selectAccountListByIdArray" resultType="com.cskaoyan.vo.Account"> select <include refid="all_column"/> from account <where> id in <!-- collection: 指循环的对象 separator: 值循环之后元素之间的分隔符 item: 泛指循环之后里面的元素的名字 open: 这个循环以什么开始 close: 这个循环以什么元素结尾 index: 指元素的下标 --> <foreach collection="array" separator="," item="id" open="(" close=")"> #{id} </foreach> </where> <!--select id,name,money,role from account where id in (#{id},#{id},#{id})--> </select> <select id="selectAccountListByIdArrayWithParam" resultType="com.cskaoyan.vo.Account"> select <include refid="all_column"/> from account <where> id in <foreach collection="ids" separator="," item="id" open="(" close=")"> #{id} </foreach> </where> </select> <select id="selectAccountListByIdList" resultType="com.cskaoyan.vo.Account"> select <include refid="all_column"/> from account <where> id in <!-- 这个地方,list 或者是 collection都可以 --> <foreach collection="collection" separator="," item="id" open="(" close=")"> #{id} </foreach> </where> </select> <select id="selectAccountListByIdListWithParam" resultType="com.cskaoyan.vo.Account"> select <include refid="all_column"/> from account <where> id in <foreach collection="ids" separator="," item="id" open="(" close=")"> #{id} </foreach> </where> </select>
-
批量插入
mapper
// 批量插入 int insertAccountBatch(@Param("accountList") List<Account> accountList);
mapper.xml
<insert id="insertAccountBatch"> <!--insert into account values (?,?,?,?),(?,?,?,?),(?,?,?,?)--> insert into account values <foreach collection="accountList" item="account" separator=","> (#{account.id},#{account.name},#{account.money},#{account.role}) </foreach> </insert>
8.8 selectKey
selectKey这个标签可以帮助我们在我们执行SQL语句之前或者之后执行一条额外的sql语句
一般是用来获取自增的主键的id的
mapper
// 插入,并且获取自增的id
int insertAccount(Account account);
mapper.xml
<insert id="insertAccount">
insert into account values (#{id},#{name},#{money},#{role})
<!--
resultType: <selectKey> 标签里面sql语句的返回类型
order: AFTER| BEFORE 表示在主SQL之前或者是之后执行
keyProperty: 表示<selectKey>这个标签里面查询到的结果封装到参数的哪个字段
keyColumn: 这个表示我们需要获取<selectKey> 这个SQL语句的结果集的哪个字段
-->
<selectKey resultType="int" order="AFTER" keyProperty="id">
select LAST_INSERT_ID()
</selectKey>
</insert>
8.9 useGeneratedKeys
只能用来获取主键id
mapper
// 插入,并且获取自增的id
int insertAccountUseGeneratedKeys(@Param("account") Account account);
mapper.xml
<insert id="insertAccountUseGeneratedKeys" useGeneratedKeys="true" keyProperty="account.id">
insert into account values (#{account.id},#{account.name},#{account.money},#{account.role})
</insert>
-
注册(注册表)
insert into user (?,?,?,?);
select id from user where ? // 有了useGeneratedKeys这个标签之后,就可以帮助我们省去中间需要查询id的这一步
获取到id
-
注册后需要去给用户送优惠券(优惠券的表 userId id )
去在优惠券这个表里面去插入一些优惠券的记录
insert into xxx