Mybatis
框架
软件的半成品
概述
- 是一个持久层框架,使用SQL语句将实体类映射到数据表,是一个半自动的ORM实现。
- O:对象
- R:关系型数据库
- M:映射
- Hibernate:全自动的ORM实现
- SSH:Struts2、Spring、Hibernate
- SSM:SpringMVC、Spring、Mybatis
- 历史
- 最早是Apache的一个开源的项目iBatis
- 2010年变更为MyBatis
- 特点
- 外部化SQL语句:把sql语句与java代码分离
- 封装化SQL语句:把sql语句封装到单独的文件中,方便管理
使用
创建一个maven项目
配置pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.neu</groupId> <artifactId>maven-demo</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <build> <plugins> <!-- 资源文件拷贝插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.7</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- java编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> <resources> <!-- 标签定义了项目中资源文件的位置和类型 --> <resource> <directory>src/main/java</directory> <includes> <!-- 这里的**表示任意多层目录,*.xml表示任意名称的.xml文件。这样一来,这个配置会递归地从src/main/java目录开始,包含所有.xml扩展名的文件。 --> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build> <!-- 依赖 --> <dependencies> <!-- 单元测试 --> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency> </dependencies> </project>
在resources目录下,添加一个SQLMapConfig.xml
<?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> <!-- 配置一个环境,一个环境对应着一个数据库,default为某一个环境的id --> <environments default="mysql"> <environment id="mysql"> <!-- 事务管理器,jdbc/managed(由外部容器管理,自己什么也不做) --> <transactionManager type="jdbc"></transactionManager> <!-- 数据源,unpooled、pooled、jndi --> <dataSource type="pooled"> <property name="url" value="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <package name="com.neu.mapper"></package> </mappers> </configuration>
使用log4j生成日志
添加依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置,在resource目录下添加文件log4j.properties
# Global logging configuration
#\u751F\u4EA7\u73AF\u5883\u914D\u7F6Einfo ERROR
log4j.rootLogger=DEBUG,stdout
# MyBatis logging configuration...
# log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
多参数的处理
使用参数名:arg0、arg1…或param1、param2
<select id="find" resultType="com.neu.entity.Dept"> select * from dept where dname=#{param1} and loc=#{param2}; </select>
使用注解@Param(推荐)
List<Dept> find(@Param("dname") String dname,@Param("loc") String loc);
<select id="find" resultType="com.neu.entity.Dept"> select * from dept where dname=#{dname} and loc=#{loc}; </select>
使用实体类封装多个参数
List<Dept> find2(Dept dept);
<select id="find2" resultType="com.neu.entity.Dept"> select * from dept where dname=#{dname} and loc=#{loc} </select>
使用map集合封装多个参数(很灵活)
List<Dept> find3(Map map); @Test public void find3() { Map<String,String> map = new HashMap(); map.put("dname","hr"); map.put("loc","sy"); List<Dept> list = deptMapper.find3(map); System.out.println(list); }
<select id="find3" resultType="com.neu.entity.Dept"> select * from dept where dname=#{dname} and loc=#{loc} </select>
#{}和${}
#{}:使用占位符的方式生成sql语句,sql语句中的列名为变量的时候,不能使用
${}:使用拼接字符串的方式生成sql语句,参数为列名的时候可以使用
List<Dept> findByOrder(String columnName);
<select id="findByOrder" resultType="com.neu.entity.Dept"> select * from dept order by ${columnName} </select>
模糊查询
//第一种 List<Dept> findByDname(String dname); //xml <select id="findByDname" resultType="com.neu.entity.Dept"> select * from dept where dname like #{dname} </select> //test @Test public void findByDname() { String dname = "h"; List<Dept> name = deptMapper.findByDname("%"+dname+"%"); System.out.println(name); }
//第二种 <select id="findByDname" resultType="com.neu.entity.Dept"> select * from dept where dname like '%${dname}%' </select> //test @Test public void findByDname() { String dname = "h"; List<Dept> name = deptMapper.findByDname(dname); System.out.println(name); }
SQL片段
定义:用来定义可重复使用的sql代码段,可以包含在其他的sql语句中
使用
定义sql片段
<sql id="getAll">select * from dept</sql>
引用sql片段
<include refid="getAll"></include>
得到插入的主键值
<selectKey>
order:决定了查询语句执行的先后
- after:先执行插入语句,后执行查询主键的sql语句(MySQL)
- before:先执行查询(生成)主键的sql语句,后执行插入语句(Oracle)
keyProperty:键的属性值,查询得到的主键值放到形参的那个属性中
resultType:查询主键值的类型
使用:
<insert id="insert"> <selectKey order="AFTER" keyProperty="deptno" resultType="int"> select last_insert_id() </selectKey> insert into dept values (null,#{dname},#{loc}) </insert>
查询返回简单数据类型
int count();//得到表的记录行数
<select id="count" resultType="java.lang.Integer">
select count(*)
from dept
</select>
String getDname(int deptno);
<select id="getDname" resultType="java.lang.String">
select dname
from dept
where deptno = #{deptno}
</select>
//返回boolean类型,如果查询记录数>0返回true,否则返回false
boolean ifExist(String dname);
<select id="ifExist" resultType="java.lang.Boolean">
select count(*)
from dept
where dname=#{dname}
</select>
动态Sql
<if test="表达式">sql片段</if>
当表达式为true的时候,输出SQL片段,否则不输出 ~~~java List<Dept> search(Dept dept); //test @Test public void search() { List<Dept> search = deptMapper.search(new Dept(null, "HR", null)); System.out.println(search); }
//xml <select id="search" resultType="com.neu.entity.Dept"> select * from dept where 1=1 <if test="deptno!=null"> and deptno = #{deptno} </if> <if test="dname!=null"> and dname=#{dname} </if> <if test="loc!=null"> and loc=#{loc} </if> </select>
<!--使用trim标签--> <trim prefix="前缀" prefixOverrides="被覆盖的前缀"></trim>
prefix:如果trim标签中不为空串,则添加该前缀,如果内容为空串,则不添加 prefixOverrides:如果trim标签内容最前面的部分是该属性的内容,则覆盖掉 ~~~xml <select id="search" resultType="com.neu.entity.Dept"> select * from dept <trim prefix="where" prefixOverrides="and"> <if test="deptno!=null"> and deptno = #{deptno} </if> <if test="dname!=null"> and dname=#{dname} </if> <if test="loc!=null"> and loc=#{loc} </if> </trim> </select>
<where></where> <!--相当于把第二个打包了-->
~~~xml select * from dept <where> <if test="deptno!=null"> and deptno = #{deptno} </if> <if test="dname!=null"> and dname=#{dname} </if> <if test="loc!=null"> and loc=#{loc} </if> </where>
<trim suffixOverrides="要覆盖的后缀字符串"></trim>
~~~xml <!--变更非空字段--> <update id="update2"> update dept <trim prefix="set" suffixOverrides=","> <if test="dname!=null"> dname=#{dname}, </if> <if test="loc!=null"> loc=#{loc}, </if> </trim> where deptno = #{deptno} </update>
<set></set><!--相当于把第4个打包了-->
~~~xml <update id="update2"> update dept <set> <if test="dname!=null"> dname=#{dname}, </if> <if test="loc!=null"> loc=#{loc}, </if> </set> where deptno = #{deptno} </update>
<foreach></foreach>:用来处理集合和数组
属性: - open:要添加的开始字符串 - close:要添加的结束字符串属性: - collection:list|connection|array - item:引用集合或数组中的当前遍历元素 - separator:分隔符 ~~~xml <insert id="batchInsert"> insert into dept values <foreach collection="list" item="dept" separator=","> (#{dept.deptno},#{dept.dname},#{dept.loc}) </foreach> </insert>
int batchDelete(int[] deptnos);
<delete id="batchDelete"> delete from dept where deptno in <foreach collection="array" item="deptno" separator="," open="(" close=")"> #{deptno} </foreach> </delete>
分页
List<Dept> getPaged(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize);
<select id="getPaged" resultType="com.neu.entity.Dept">
<include refid="selectAll"></include>
order by deptno
limit ${(pageNum-1)*pageSize},#{pageSize}
</select>
列名与与属性名不一致
在SQL语句中为列起别名,别名与属性名一致
使用
<resultMap>
来进行映射//如果列名和属性名相同,可以自动映射,不用写 <resultMap id="deptMap" type="com.neu.entity.Dept"> <result column="dname" property="dname2"></result </resultMap> <select id="getAll" resultMap="deptMap"> select deptno,dname,loc from dept; </select>
<id>
:用来映射主键<result>
:用来映射非主键- column:列名
- properly:实体类的属性名
实体关联
<association>
:关联(实体),表示一对一关系(emp=>dept)实体类
package com.neu.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class Emp { private Integer EMPNO; private String ENAME; private String JOB; private Integer MGR; private Date HIREDATE; private Double SAL; private Double COMM; private Integer DEPTNO; private Dept dept; }
方法一:表连接,手工映射
<select id="getById" resultMap="empMap"> select e.* ,d.dname,d.loc from emp e join dept d on e.deptno = d.deptno where empno = #{empno} </select> <resultMap id="empMap" type="com.neu.entity.Emp"> <id column="EMPNO" property="EMPNO"></id> <result column="ENAME" property="ENAME"></result> <result column="JOB" property="JOB"></result> <result column="HIREDATE" property="HIREDATE"></result> <result column="MGR" property="MGR"></result> <result column="SAL" property="SAL"></result> <result column="COMM" property="COMM"></result> <result column="DEPTNO" property="DEPTNO"></result> <association property="dept" javaType="com.neu.entity.Dept"> <id column="deptno" property="deptno"></id> <result column="dname" property="dname2"></result> <result column="loc" property="loc"></result> </association> </resultMap>
- 方法二:通过方法实现映射(列名到实体的映射) - ~~~xml <resultMap id="empMap" type="com.neu.entity.Emp"> <result column="DEPTNO" property="DEPTNO"></result> <association property="dept" column="DEPTNO" select="com.neu.mapper.DeptMapper.getById"> </association> </resultMap> <select id="getById" resultMap="empMap"> select * from emp where empno=#{empno} </select>
:关联集合,一对多的关系 package com.neu.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @AllArgsConstructor @NoArgsConstructor public class Dept { private Integer deptno; private String dname2; private String loc; //新增 private List<Emp> emps; }
<resultMap id="deptMap" type="com.neu.entity.Dept"> <collection property="emps" column="deptno" select="com.neu.mapper.EmpMapper.getByDeptNo"> </collection> </resultMap> <select id="getById" resultMap="deptMap"> select * from dept where deptno = #{deptno} </select>
使用注解替代mapper文件
使用注解
@Select("select * from emp") List<Emp> getAll(); @Delete("delete from emp where empno = #{empno}") int delete(int empno);
长SQL语句的写法
//拼串 @Update("update emp set sal = #{sal}"+ " where empno = #{empno}") int update(Emp emp) //数组 @Update({"update emp set sal = #{sal}", " where empno = #{empno}"}) int update(Emp emp)
ResultMap(mybatis 3.5.4)
@Select("select * from emp") List<Emp> getAll(); @Select("select * from emp where empno=#{empno}") @Results(id = "map2",value={ @Result(column = "empno",property = "EMPNO",id = true), @Result(column = "ename",property = "ENAME"), @Result(column = "job",property = "JOB"), @Result(property = "dept",column = "deptno" ,many = @Many(select = "com.neu.mapper.DeptMapper.getById")) }) Emp getById(int empno);
延迟加载
定义:在数据与对象进行mapping的映射操作时,只有在真正使用该对象的时候才进行mapping操作,以减少数据库开销,从而提高系统性能
默认情况下mybatis没有开启延迟加载功能,如果要开启,需要配置SqlMapConfig.xml
<settings> <!--开始延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> <!--取消积极加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
缓存
定义:是一种以空间换时间设计理念,利用内存或硬盘空间来提高数据检索速度的有效手段之一
一级缓存:默认开启,把数据缓存到当前会话中(SqlSession)
二级缓存:默认没有开启,需要手工开启,修改SqlMapConfig.xml文件
<!--开启二级缓存--> <setting name="cacheEnabled" value="true"/>
在需要缓存的mapper文件中设置
<cache></cache>
注意:二级缓存对象要实现可序列化接口(Serializable)
注意:使用缓存时,需要考虑缓存数据的时效性和一致性