Mybatis
常見
#{} 和 ${}
-
{}:sql占位符
- ${}:字符替換华畏,properties中的變量占位符
Mybatis使用方式
- 方式一:
Mapper類
public interface StudentMapper {
List<Student> selectAll();
}
StudentMapper.xml
<select id="selectAll" resultMap="BaseResultMap">
select `name`, code
from student
</select>
- 方式二:
Mapper類:
public interface StudentMapper{
@Select("select * from student")
List<Student> selectAll();
}
- 方式三
Mapper類:
public interface StudentMapper{
@SelectProvider(type= StudentProvider.class,method="selectStudentById")
Student selectStudentById(@Param(value="code") String code);
}
StudentProvider
public class StudentProvider {
public String selectStudentById(Map<String, Object> para){
return new SQL(){{
SELECT("*");
FROM("student");
WHERE("code="+para.get("code"));
}}.toString();
}
}
獲取自增長主鍵id
Mysql:
- 方式一:
<insert id="insert" parameterType="com.mi.learn.mybatis.domin.Afinfo">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into afinfo (`name`, age, birth,
sex, memo)
values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE},
#{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
</insert>
- 方式二
<insert id="insertAfinfo" parameterType="com.mi.learn.mybatis.domin.Afinfo" useGeneratedKeys="true" keyProperty="id">
insert into afinfo (`name`, age, birth,
sex, memo)
values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE},
#{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
</insert>
Oracle:
// 這個是創(chuàng)建表的自增序列
CREATE SEQUENCE student_sequence
INCREMENT BY 1
NOMAXVALUE
NOCYCLE
CACHE 10;
// 方式一,使用 `<selectKey />` 標(biāo)簽 + BEFORE
<insert id="add" parameterType="Student">
<selectKey keyProperty="student_id" resultType="int" order="BEFORE">
select student_sequence.nextval FROM dual
</selectKey>
INSERT INTO student(student_id, student_name, student_age)
VALUES (#{student_id},#{student_name},#{student_age})
</insert>
// 方式二跳夭,使用 `<selectKey />` 標(biāo)簽 + AFTER
<insert id="save" parameterType="com.threeti.to.ZoneTO" >
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" >
SELECT SEQ_ZONE.CURRVAL AS id FROM dual
</selectKey>
INSERT INTO TBL_ZONE (ID, NAME )
VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR})
</insert>
Mapper傳遞多個參數(shù)
- 使用Map傳遞
Afinfo selectAfinfoWithMap(Map<String,Object> map);
<select id="selectAfinfoWithMap" parameterType="Map" resultType="com.mi.learn.mybatis.domin.Afinfo">
select id, `name`, age, birth, sex, memo
from afinfo
where `name` = #{name} and age=#{age}
</select>
- 使用@Param傳遞
Afinfo selectAfinfoWithParam(@Param("name") String name,
@Param("age") Integer age
);
<select id="selectAfinfoWithParam" resultType="com.mi.learn.mybatis.domin.Afinfo">
select id, `name`, age, birth, sex, memo
from afinfo
where `name` = #{name} and age=#{age}
</select>
- 不使用@Param
Afinfo selectAfinfoWithObject(String name,
Integer age);
<select id="selectAfinfoWithObject" resultType="com.mi.learn.mybatis.domin.Afinfo">
select id, `name`, age, birth, sex, memo
from afinfo
where `name` = #{param1} and age=#{param2}
</select>
批量插入
- for循環(huán)調(diào)用單條插入
for (int i=0;i<5; i++){
mapper.insertAfinfo(afinfo);
}
<insert id="insertAfinfo" parameterType="com.mi.learn.mybatis.domin.Afinfo" useGeneratedKeys="true" keyProperty="id">
insert into afinfo (`name`, age, birth,
sex, memo)
values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=DATE},
#{sex,jdbcType=CHAR}, #{memo,jdbcType=VARCHAR})
</insert>
- 批量插入
mapper.insertBatch(list);
<insert id="insertBatch" parameterType="list" useGeneratedKeys="true" keyProperty="id">
insert into afinfo (`name`, age, birth,
sex, memo) values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.name,jdbcType=VARCHAR}, #{item.age,jdbcType=INTEGER}, #{item.birth,jdbcType=DATE},
#{item.sex,jdbcType=CHAR}, #{item.memo,jdbcType=VARCHAR})
</foreach>
</insert>
控制臺打印sql日志
logging.level.com.mi.learn.mybatis.mapper=debug
Mybatis緩存
# 開啟緩存
mybatis.configuration.cache-enabled=true
Mybatis和Hibernate
- Hibernate 全自動ORM映射工具宜猜,可以查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象烈炭,可以根據(jù)對象關(guān)系模型直接獲取
- Mybatis屬于半自動ORM映射工具,在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時宝恶,需要手動編寫SQL
轉(zhuǎn)義字符
轉(zhuǎn)義字符 | 字符 | 含義 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | 與 |
' | ' | 單引號 |
" | " | 雙引號 |
或者
<![CDATA[ ]]>
XML標(biāo)簽
<select></select>
<insert></insert>
<update></update>
<delete></delete>
<resultMap></resultMap>
<parameterMap></parameterMap>
<sql></sql>
<include></include>
<selectKey></selectKey>
sql符隙、include標(biāo)簽
List<Afinfo> selectAfinfoWithObject(String name,
Integer age);
<sql id="student_element">
id,`name`,age,birth,sex,memo
</sql>
<select id="selectAfinfoWithObject" resultType="com.mi.learn.mybatis.domin.Afinfo">
select <include refid="student_element"></include>
from afinfo
where `name` = #{param1} and age=#{param2}
</select>
xml和Mapper接口的工作原理,Mapper接口參數(shù)不通垫毙,能否重載
- xml文件與Mapper類通過namespace來一一對應(yīng)
- Mapper接口是沒有實現(xiàn)類的霹疫,通過調(diào)用方法,mapper類的包名+接口名為key
- 如:com.mi.learn.mybatis.mapper.AfinfoMapper.insert
- 可以找到namespace為com.mi.learn.mybatis.mapper.AfinfoMapper下
- id為insert的MappedStatement(映射語句)
- 每個 select综芥、update丽蝎、delete、insert都會被解析成一個MapperStatement(映射語句)
- 如:com.mi.learn.mybatis.mapper.AfinfoMapper.insert
- Mapper接口是不能重載的,因為是通過包名+方法名去保存和尋找
- Mapper接口的工作原理是JDK動態(tài)代理
- Mybatis運行時會通過JDK動態(tài)代理把Mapper接口生成代理proxy對象
- 代理對象proxy會攔截接口方法
- 改成執(zhí)行MapperStatement所對應(yīng)的sql
- 將sql執(zhí)行的結(jié)果返回
Mybits分頁
- RowBounds對象進行分頁屠阻,對ResultSet結(jié)果集執(zhí)行內(nèi)存分頁
- 分頁插件原理是使用Mybatis提供的插件接口红省,實現(xiàn)自定義插件,在插件的攔截方法中攔截待執(zhí)行的sql国觉,重寫sql吧恃,添加對應(yīng)的物理分頁和物理分頁參數(shù)
Mybatis的插件運行原理帕胆,怎么編寫一個插件
- Mybatis只能編寫針對ParameterHandler务豺、ResultSetHandler贰谣、StatementHandler呈础、Executor這四種接口插件
package com.mi.learn.mybatis.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Component
@Intercepts({@Signature(type= Executor.class, method = "select", args = {MappedStatement.class,Object.class})})
public class MybatisInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理對象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法參數(shù)
// do something ...... 方法攔截前執(zhí)行代碼塊
Object result = invocation.proceed();
// do something .......方法攔截后執(zhí)行代碼塊
return result;
}
public Object plugin(Object target) {
return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return intercept(new Invocation(target, method, args));
}
});
}
}
Mybatis一對多、多對一
-
一對多
List<StudentExam> selectStudentExam(); //StudentExam @Data public class StudentExam { private String name; private String code; private List<Exam> examList; } // Exam @Data public class Exam { private String code; private String subject; private String score; }
```xml
<resultMap id="StudentExamResultMap" type="com.mi.learn.mybatis.bean.StudentExam">
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="code" jdbcType="VARCHAR" property="code" />
<collection property="examList" ofType="com.mi.learn.mybatis.domin.Exam" column="code">
<result column="code" jdbcType="VARCHAR" property="code"/>
<result column="subject" jdbcType="VARCHAR" property="subject"/>
<result column="score" jdbcType="VARCHAR" property="score"/>
</collection>
</resultMap>
<select id="selectStudentExam" resultMap="StudentExamResultMap">
select s.* , e.*
from student s , exam e
where s.code = e.code
order by s.code
</select>
```
-
多對一
List<ExamStudent> selectExamStudent(); //StudentExam @Data public class StudentExam { private String name; private String code; private List<Exam> examList; } //Exam @Data public class Exam { private String code; private String subject; private String score; }
```xml
<resultMap id="ExamStudentResultMap" type="com.mi.learn.mybatis.bean.ExamStudent">
<result column="code" jdbcType="VARCHAR" property="code"/>
<result column="subject" jdbcType="VARCHAR" property="subject"/>
<result column="score" jdbcType="VARCHAR" property="score"/>
<association property="student" javaType="com.mi.learn.mybatis.domin.Student">
<result column="code" jdbcType="VARCHAR" property="code"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
</association>
</resultMap>
<select id="selectExamStudent" resultMap="ExamStudentResultMap">
select s.* , e.*
from student s , exam e
where s.code = e.code
order by s.code
</select>
```
Mybatis延遲加載
# 開啟緩存
mybatis.configuration.cache-enabled=true
# 延遲加載
mybatis.configuration.lazy-loading-enabled=true
# 關(guān)閉積極加載
mybatis.configuration.aggressive-lazy-loading=false
List<StudentExam> selectLazyStudent();
List<Exam> selectExamByCode(@Param("code") String code);
@Test
@Transactional
public void selectLazyStudentTest(){
List<StudentExam> res = mapper.selectLazyStudent();
log.info(JSON.toJSONString(res));
log.info("==========================================");
List<Exam> examList1 = mapper.selectExamByCode("001");
List<Exam> examList2 = mapper.selectExamByCode("002");
List<Exam> examList3 = mapper.selectExamByCode("003");
log.info("==========================================");
log.info("examList1:{}",JSON.toJSONString(examList1));
log.info("examList1:{}",JSON.toJSONString(examList2));
log.info("examList1:{}",JSON.toJSONString(examList3));
}
<resultMap id="StudentExamLazyResultMap" type="com.mi.learn.mybatis.bean.StudentExam">
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="code" jdbcType="VARCHAR" property="code" />
<association property="examList" javaType="List" select="com.mi.learn.mybatis.mapper.StudentMapper.selectExamByCode" column="code"/>
</resultMap>
<select id="selectLazyStudent" resultMap="StudentExamLazyResultMap">
select s.*
from student s
order by s.code
</select>
<select id="selectExamByCode" resultType="com.mi.learn.mybatis.domin.Exam" parameterType="string">
select * from exam where exam.code = #{code}
</select>
日志:
com.mi.learn.mybatis.StudentMapperTest : ==========================================
com.mi.learn.mybatis.StudentMapperTest : ==========================================
com.mi.learn.mybatis.StudentMapperTest : examList1:[{"code":"001","score":"80","subject":"數(shù)學(xué)"},{"code":"001","score":"90","subject":"語文"},{"code":"001","score":"90","subject":"英語"}]
com.mi.learn.mybatis.StudentMapperTest : examList2:[{"code":"002","score":"75","subject":"數(shù)學(xué)"},{"code":"002","score":"85","subject":"語文"},{"code":"002","score":"85","subject":"英語"}]
com.mi.learn.mybatis.StudentMapperTest : examList3:[{"code":"003","score":"80","subject":"英語"}]
并沒有打印查詢Exam表的sql語句
Mybatis中xml的id是否能一樣
- 查找是按namespace+id來作為Map<key,MappedStatement>的key
- 設(shè)置了namespace蕴侣,namespace不一樣很澄,id一樣是可以的
- 沒設(shè)置namespace锄弱,id不能一樣呻引。
Executor
- SimpleExecutor
- 每執(zhí)行一次update或select礼仗,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象
- ReuseExecutor
- 執(zhí)行update或select逻悠,以sql作為key查找Statement對象元践,存在就使用,不存在就創(chuàng)建蹂风,用完后卢厂,不關(guān)閉Statement對象乾蓬,而是放置于Map<String, Statement>內(nèi)惠啄,供下一次使用。簡言之任内,就是重復(fù)使用Statement對象撵渡。
- BatchExecutor
- 執(zhí)行update(沒有select,JDBC批處理不支持select)死嗦,將所有sql都添加到批處理中(addBatch())趋距,等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象越除,每個Statement對象都是addBatch()完畢后节腐,等待逐一執(zhí)行executeBatch()批處理。與JDBC批處理相同摘盆。
Mybatis映射Enum枚舉
SexEnum
public enum SexEnum {
SEX_NO_KNOW(0,"未知"),
SEX_ENUM_MAN(1,"男"),
SEX_ENUM_WOMAN(2,"女");
private Integer sex;
private String context;
SexEnum(int sex, String context) {
this.sex = sex;
this.context = context;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Integer getSex() {
return sex;
}
public void setContext(String context) {
this.context = context;
}
public String getContext() {
return context;
}
}
Afinfo
@Data
@ToString
public class Afinfo implements Serializable {
private Integer id;
private String name;
private Integer age;
private Date birth;
private SexEnum sex;
private String sexContext;
private String memo;
private static final long serialVersionUID = 1L;
}
AfinfoMapper.xml
<resultMap id="BaseResultMap" type="com.mi.learn.mybatis.domin.Afinfo">
<result column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="birth" jdbcType="DATE" property="birth" />
<result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.mi.learn.mybatis.myenum.SexEnum" />
<result column="memo" jdbcType="VARCHAR" property="memo" />
</resultMap>
Test
List<Afinfo> afinfoList11 = mapper.selectAfinfoWithMap(map);
afinfoList11.forEach(item->{
SexEnum sexEnum = item.getSex();
log.info(sexEnum.getContext());
});
log日志
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 女
com.mi.learn.mybatis.AfinfoMapperTest : 未知
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
com.mi.learn.mybatis.AfinfoMapperTest : 男
數(shù)據(jù)庫中的數(shù)據(jù)
sex
1
1
1
1
1
2
0
1
1
1
- 在ResultMap中加入固定的
typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
- 加入
javaType="com.mi.learn.mybatis.myenum.SexEnum"
翼雀,SexEnum為Enum包 - Afinfo的sex屬性改成SexEnum引用對象
使用
pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
配置自動生成代碼
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!--允許移動生成的文件 -->
<verbose>true</verbose>
<!-- 是否覆蓋 -->
<overwrite>true</overwrite>
<!-- 自動生成的配置 -->
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
generatorConfig.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--加載配置文件,為下面讀取數(shù)據(jù)庫信息準(zhǔn)備-->
<properties resource="application.properties"/>
<!--defaultModelType="flat" 大數(shù)據(jù)字段孩擂,不分表 -->
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="autoDelimitKeywords" value="true" />
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="utf-8" />
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
<!-- 注釋 -->
<commentGenerator >
<property name="suppressAllComments" value="true"/><!-- 是否取消注釋 -->
<property name="suppressDate" value="false" /> <!-- 是否生成注釋代時間戳-->
</commentGenerator>
<!--數(shù)據(jù)庫鏈接地址賬號密碼-->
<jdbcConnection driverClass="${spring.datasource.driver-class-name}"
connectionURL="${spring.datasource.url}"
userId="${spring.datasource.username}"
password="${spring.datasource.password}">
</jdbcConnection>
<!-- 類型轉(zhuǎn)換 -->
<javaTypeResolver>
<!-- 是否使用bigDecimal狼渊, false可自動轉(zhuǎn)化以下類型(Long, Integer, Short, etc.) -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model類存放位置-->
<javaModelGenerator targetPackage="com.mi.learn.mybatis.domin" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成mapxml文件 -->
<sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources/" >
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成mapxml對應(yīng)client,也就是接口dao -->
<javaClientGenerator targetPackage="com.mi.learn.mybatis.mapper" targetProject="src/main/java" type="XMLMAPPER" >
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<table tableName="afinfo" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table>
</context>
</generatorConfiguration>
application.properties
# 端口
server.port=8001
# xxxMapper.xml文件的路徑
mybatis.mapper-locations=classpath:mapping/*.xml
# 日志顯示sql
logging.level.com.mi.learn.mybatis.mapper=debug
# mysql數(shù)據(jù)庫用戶名
spring.datasource.username=root
# mysql數(shù)據(jù)庫用戶密碼
spring.datasource.password=123456
# mysql連接url
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
# mysql連接驅(qū)動
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver