- 创建核心配置文件(可从MyBatis官方文档获取)
<?xml version="1.0" encoding="UTF-8" ?> <!-- 存在于MyBatis官方文档中 --> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 核心配置文件中的标签是有序的,应当按顺序配置 --> <!-- 引入properties文件 --> <properties resource="jdbc.properties"/> <typeAliases> <!-- 配置别名,简化mapper.xml中的类名引用 --> <!-- alias默认为类名,且不区分大小写 --> <typeAlias type="indi.beta.pojo.User" alias="user"/> <!-- 将某个软件包下所有类都使用其类名作为别名,用于类过多的情况 --> <package name="indi.beta.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <!-- transactionManager: JDBC(原生的事务管理方式,手动提交) | MANAGED --> <transactionManager type="JDBC"/> <!-- dataSource: POOLED(使用连接池) | UNPOOLED(不使用连接池) | JNDI(使用上下文数据源) --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 配置映射文件 --> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
- 创建映射文件
- 映射文件: java对象与数据库对象之间映射关系。
- 一般来说,一张表对应了一个映射文件。推荐对每张表建立单独的mapper
- 两个一致
- 映射文件namespace与全类名必须一致
- 映射文件中SQL语句的id与mapper接口中的方法名一致
- 添加一条映射
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="indi.beta.mapper.UserMapper"> <insert id="insertUser"> insert into t_table(id, name) values (3001, 'AlphaDog') </insert> <delete id="deleteUserById"> delete from t_table where id=3001 </delete> <!-- 查询语句必须设置resultType或rsultMap属性,从而实现查询结果与java类的映射 --> <!-- resultType用于数据表字段与java类属性一致的情况,resultMap用于不一致的情况 --> <select id="queryUserById" resultType="indi.beta.pojo.User"> select * from t_table where id=15 </select> </mapper>
- 在mybatis配置中导入该映射
<!-- 配置映射文件 --> <mappers> <mapper resource="mappers/UserMapper.xml"/> <!-- 将目录下所有mapper.xml全部导入,要求被导入的xml包名与对应的接口类包名相同,且文件名(不含后缀)相同 --> <package name="indi.beta.mapper"/> </mappers>
- 执行
// 加载核心配置文件 InputStream resource = Resources.getResourceAsStream("mybatis-config.xml"); // 获取SqlSessionFactoryBuilder SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 获取SqlSessionFactory SqlSessionFactory factory = builder.build(resource); // 获取SqlSession。可以为openSession提供参数true实现自动提交 SqlSession sqlSession = factory.openSession(); // 获取mapper接口对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 执行mapper中的方法 int result = mapper.insertUser(); sqlSession.commit(); System.out.println("Row affected: " + result);
- 配置log4j
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n"/> </layout> </appender> <logger name="java.sql"> <level value="debug"/> </logger> <logger name="org.apache.ibatis"> <level value="info"/> </logger> <root> <level value="debug"/> <appender-ref ref="STDOUT"/> </root> </log4j:configuration> <!-- <setting name="logImpl" value="LOG4J"/>添加到mybatis配置文件中 -->
- 通过#{}或${}获取单个参数
<!-- User queryUserById(int id) --> <select id="queryUserById" resultType="user"> select * from t_table where id=#{id} </select>
- #{}与其中包括的变量名无关,但不能缺省。推荐设置为方法的参数名
- 字面量为字符串时,应当使用单引号将${}包裹起来
- 获取单个字面量时,${}与#{}时等效的
- ${}的底层实现是字符串拼接,#{}底层实现是占位符赋值。${}可能出现sql注入
- #{}会被替换为?,如果sql中本身就含有sql,则会产生冲突
- #{}会自动将值的左右两侧添加单引号,这是很多问题的根源
- 获取多个参数(自动)
<!-- User queryUserById(int id) --> <select id="checkUser" resultType="user"> select * from t_table where id=#{arg0} and name=#{arg1} </select>
- 使用arg作为键时,从0开始。使用param作为键时,从1开始。二者可以混用,但不推荐
- 键的创建是自动的
- 也可以使用${},依然需要注意字符串拼接问题
- 获取多个参数(手动)
<!-- User queryUserById(int id, String name) --> <select id="checkUser" resultType="user"> select * from t_table where id=#{my_key_1} and name=#{my_key_2} </select>
- 将方法的参数类型设置为Map,并将键设置为字符串
- 在mapper.xml中使用自定义的键即可访问
- 获取实体类对象(推荐)
<!-- User insertUser(User user) --> <!-- User {int id; String name;} --> <select id="checkUser" resultType="user"> select * from t_table where id=#{id} and name=#{name} </select>
- 直接使用实体类的属性进行访问
- 需要提供getter和setter -使用@Param注解为变量赋键(推荐)
<!-- User checkUser(@Param("userId") int id, @Param("UserName") String name); --> <select id="checkUser" resultType="user"> select * from t_table where id=#{userId} and name=#{userName} </select>
- 即使使用了@Param注解,依然可以使用param作为键来获取参数
- 聚合函数
- sql中的聚合函数在mybatis中依然是可用的,只是需要将resultType指定为相应的返回值类型
- 将结果返回为Map集合
<!-- Map<String, Objects> getUserByIdToMap(int id);--> <select id="getUserByIdToMap" resultType="Map"> select * from t_table where id=#{id} </select>
- 返回值的键为字段名,返回值的值为属性值
- 返回记录有多条时,需要将方法返回值类型改为List<Map<String, Objects>>
- 返回记录有多条时,可以在mapper方法前通过@MapKey注解将某个字段设置为键
- 模糊查询
- 由于#{}采用的是占位符赋值方式,在解析时首先被替换为?。在模糊查询中,该?会出现在字符串中,被认为是字符串的一部分,导致占位符无法被赋值
- 解决方案1: 采用${}替换#{}
<!-- List<User> getUserByLike(String name);--> <select id="getUserByLike" resultType="user"> select * from t_table where name like '${name}%' </select>
- 解决方案2: 采用sql的concat函数进行拼接
<!-- List<User> getUserByLike(String name);--> <select id="getUserByLike" resultType="user"> select * from t_table where name like concat(#{name}, '%') </select> ```xml
- 解决方案3: 采用双引号将模糊符号包裹起来
<!-- List<User> getUserByLike(String name);--> <select id="getUserByLike" resultType="user"> select * from t_table where name like #{name}"%" </select>
- 批量删除
<!-- int batchDelete(String ids);--> <delete id="batchDelete"> delete from t_table where id in (${ids}) </delete>
- 此时不能使用#{}, 因为它会将多个值拼接为1个字符串,导致sql总是找不到匹配的记录。当条件字段是数值型时,会报数值转换异常
- 动态表名
select * from ${t_name}
- 动态表名只能使用${},因为表名不能为字符串,而#{}会自动添加单引号
- 添加时获取自增主键
- 由于insert的返回值是受影响行数,因此只能需要考虑将主键值通过mapper方法的参数传递出来
<!-- void insertUserReturnAutoIncrementKey(User user);--> <insert id="insertUserReturnAutoIncrementKey" useGeneratedKeys="true" keyProperty="id"> insert into t_table values(null, #{name}) </insert>
- 利用java传引用的特性,传入的参数不需要设置id,而会通过mybatis自动将其值设置为自增主键值
- 通过java语句可以查看user被赋予的id
- 使用字段别名
- 当字段名与属性名不一致时,该属性无法被赋值,因此最终结果为null
- 通过将sql字段设置与属性名一致的别名可以解决该问题
- 通过核心配置文件中的setting进行配置(官方的下划线转驼峰)
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> - _**要求属性名与字段名必须对应,除了命名规则导致的不一致外**_
- 通过resultMap
<!-- 配置映射规则 --> <!-- type为数据库对应的java类的全类名或alias --> <resultMap id="empResultMap" type="employee"> <!-- id用于配置主键映射 --> <!-- propertu为属性名,column为字段名 --> <id property="employeeId" column="employee_id"/> <!-- result用于配置非主键映射 --> <result property="firstName" column="first_name"/> <result property="lastName" column="last_name"/> <result property="email" column="email"/> <result property="phoneNumber" column="phone_number"/> <result property="hireDate" column="hire_date"/> <result property="jobId" column="job_id"/> <result property="salary" column="salary"/> <result property="commissionPct" column="commission_pct"/> <result property="managerId" column="manager_id"/> <result property="departmentId" column="department_id"/> </resultMap> <!-- 使用resultMap代替resultType进行映射 --> <select id="getEmployeeById" resultMap="empResultMap"> select * from employees where employee_id=#{id} </select>
- 解决多对一(多个字段对应1个属性)的映射关系: 级联赋值
<select id="getEmployeeWithJobHistoryById" resultMap="simplifiedEmployeeMap"> select e.employee_id e_id, concat(e.first_name, e.last_name) name, j.employee_id j_e_id, j.start_date j_s_date, j.end_date j_e_date, j.job_id j_j_id, j.department_id j_d_id from employees e right join job_history j on e.employee_id = j.employee_id where e.employee_id=#{id}; </select> <resultMap id="simplifiedEmployeeMap" type="simplifiedEmployee"> <!-- SimplifiedEmployee包含id name jobHistory属性,其中jobHistory为JobHistory对象 --> <id property="id" column="eid"/> <result property="name" column="name"/> <result property="jobHistory.employeeId" column="j_e_id"/> <result property="jobHistory.startDate" column="j_s_date"/> <result property="jobHistory.endDate" column="j_e_date"/> <result property="jobHistory.jobId" column="j_j_id"/> <result property="jobHistory.departmentId" column="j_d_id"/> </resultMap>
- 解决多对一(多个字段对应1个属性)的映射关系: association
<select id="getEmployeeWithJobHistoryById" resultMap="simplifiedEmployeeMap"> select e.employee_id e_id, concat(e.first_name, e.last_name) name, j.employee_id j_e_id, j.start_date j_s_date, j.end_date j_e_date, j.job_id j_j_id, j.department_id j_d_id from employees e right join job_history j on e.employee_id = j.employee_id where e.employee_id=#{id}; </select> <resultMap id="simplifiedEmployeeMap" type="simplifiedEmployee"> <id property="id" column="eid"/> <result property="name" column="name"/> <association property="jobHistory" javaType="indi.beta.bean.JobHistory"> <result property="employeeId" column="j_e_id"/> <result property="startDate" column="j_s_date"/> <result property="endDate" column="j_e_date"/> <result property="jobId" column="j_j_id"/> <result property="departmentId" column="j_d_id"/> </association> </resultMap>
- 解决多对一(多个字段对应1个属性)的映射关系: 分步查询
- 建立第一个查询,获取第二个查询所需要的关键字
<!-- SimplifiedEmployee getEmployeeWithDepartment(@Param("eid") int id);--> <select id="getEmployeeWithDepartment" resultMap="employeeWithDepartmentMap"> select employee_id, concat(first_name, ' ', last_name) name, department_id from employees where employee_id=#{eid} </select> <resultMap id="employeeWithDepartmentMap" type="simplifiedEmployee"> <id property="id" column="employee_id"/> <result property="name" column="name"/> <!-- Department getDepartmentById(@Param("did") int id);--> <!-- association中的column是连接条件,select是子查询对应的mapper方法的全路径 --> <association property="department" select="indi.beta.mapper.DepartmentMapper.getDepartmentById" column="department_id"/> </resultMap>
- 建立第二个查询(子查询),将第一个查询中缺少的字段查询出来
<select id="getDepartmentById" resultType="department"> select * from departments where department_id=#{did} </select>
- 延迟加载。如无必要,不执行子查询,从而提升查询效率
<setting name="lazyLoadingEnabled" value="true"/>
- 可通过association的属性fetchType="eager"单独设置某个子查询为非延迟加载
- 解决一对多查询(某个属性的查询结果为多条记录): collection
<select id="getDepartmentWithEmpById" resultMap="getDepartmentWithEmpByIdMap"> select * from departments d left join employees e on d.department_id=e.department_id where d.department_id=#{did} </select> <resultMap id="getDepartmentWithEmpByIdMap" type="department"> <id property="departmentId" column="department_id"/> <result property="departmentName" column="department_name"/> <result property="managerId" column="manager_id"/> <result property="locationId" column="location_id"/> <!-- 设置属性的属性名以及对应的java类。employees是department下的一个list集合 --> <collection property="employees" ofType="simplifiedEmployee"> <id property="id" column="employee_id"/> <result property="name" column="first_name"/> </collection> </resultMap>
- 解决一对多查询(某个属性的查询结果为多条记录): 分步查询
- 主查询
<select id="getDepartmentWithEmpById" resultMap="getDepartmentWithEmpByIdMap"> select * from departments where department_id=#{did} </select> <resultMap id="getDepartmentWithEmpByIdMap" type="department"> <id property="departmentId" column="department_id"/> <result property="departmentName" column="department_name"/> <result property="managerId" column="manager_id"/> <result property="locationId" column="location_id"/> <collection property="employees" select="indi.beta.mapper.SimplifiedEmployeeMapper.getEmployeeOfDepartment" column="department_id"/> </resultMap>
- 子查询
<select id="getEmployeeOfDepartment" resultMap="employeeMap"> select employee_id id, concat(first_name, last_name) name, department_id from employees where department_id=#{did} </select> <resultMap id="employeeMap" type="simplifiedEmployee"> <id property="id" column="id"/> <result property="name" column="name"/> </resultMap>
- if
<!-- List<SimplifiedEmployee> getEmployeeByCondition(SimplifiedEmployee employee);--> <select id="getEmployeeByCondition" resultType="simplifiedEmployee"> select employee_id id, concat(first_name, ' ', last_name) name, department_id from employees where 1=1 <if test="id != null"> employee_id=#{id} </if> <if test="departmentId != null"> and department_id=#{departmentId} </if> </select>
- if用于自适应添加条件
- 1=1是恒成立条件,用于避免第一个if不满足时导致的"where and"语法错误
- if中的属性来自于mapper方法的形参的属性
- where
<!-- List<SimplifiedEmployee> getEmployeeByCondition(SimplifiedEmployee employee);--> <select id="getEmployeeByCondition" resultType="simplifiedEmployee"> select employee_id id, concat(first_name, ' ', last_name) name, department_id from employees <where> <if test="id != null"> employee_id=#{id} </if> <if test="departmentId != null"> and department_id=#{departmentId} </if> </where> </select>
- where标签中有为真的条件时,自动在sql中追加where条件;否则不会生成where
- where标签可以自适应删除条件连接符,例如and or,不需要再使用1=1
- where不能将内容后的and or去除,因此十分不推荐将and或or写在if的末尾
- trim
<!-- List<SimplifiedEmployee> getEmployeeByCondition(SimplifiedEmployee employee);--> <select id="getEmployeeByCondition" resultType="simplifiedEmployee"> select employee_id id, concat(first_name, ' ', last_name) name, department_id from employees <trim prefix="where" suffix="" prefixOverrides="and|or" suffixOverrides="and|or"> <if test="id != null"> employee_id=#{id} </if> <if test="departmentId != null"> and department_id=#{departmentId} </if> </trim> </select>
- 自动追加where,自动删除特定的开头或结尾
- trim只会在内部存在内容时才会生效
- prefixOverrides和suffixOverrides用于删除,多个值时使用"|"隔开即可
- prefix和suffix用于添加
- choose when otherwise
<!-- List<SimplifiedEmployee> getEmployeeByCondition(SimplifiedEmployee employee);--> <select id="getEmployeeByCondition" resultType="simplifiedEmployee"> select employee_id id, concat(first_name, ' ', last_name) name, department_id from employees <where> <choose> <when test="id != null and id != '' and id != 0"> employee_id=#{id} </when> <when test="departmentId != null and departmentId != 0"> department_id=#{departmentId} </when> <otherwise> name=#{name} </otherwise> </choose> </where> </select>
- choose when otherwise相当于java的if-else结构
- 必须使用where进行包裹,否则会出现sql语句中where的缺失
- 当有条件满足时,后续的所有条件都不再执行,也不会添加到sql中
- foreach
<delete id="deleteBatchEmployee"> delete from t_table where id in <foreach collection="eidList" item="eid" separator="," open="(" close=")"> #{eid} </foreach> </delete>
<insert id="insertBatchEmployee"> insert into t_table values <foreach collection="employees" item="employee" separator=","> (null, #{employee.name}) </foreach> </insert>
- seperator: 设置分隔符,前后会自动添加空格
- open: 设置左侧开始符号
- close: 设置右侧关闭符号
- collection: 集合名称
- item: 集合中每次迭代的变量的临时名称
- foreach会自动添加占位符"?"
- 对于insert,不应当设置open="(" close=")",该操作是针对整体的,会导致sql语法错误。括号应当在foreach内部手动添加
- sql
<sql id="empWithDept">employee_id, concat(first_name, ' ', last_name) name, department_id</sql>
- 将常用的查询字段进行提取,并使用别名进行替代
<select id="getEmployeeWithDepartment" resultMap="employeeWithDepartmentMap"> select <include refid="empWithDept"/> from employees where employee_id=#{eid} </select>
- 在需要引用的地方使用使用别名代替复杂的字段
- 一级缓存(默认开启)
- SqlSession级别。通过同一个SqlSession查询的数据会被缓存,下次访问时直接从缓存获取
- 失效场景
- 不同的SqlSession进行查询
- 同一个SqlSession但查询条件不同
- 同一个SqlSession的两次相同查询之间发生了增删改(无论是否发生在查询的记录上)
- 使用了手动清空
- 二级缓存
- SqlSessionFactory级别。通过同一个工厂创建的Sqlsession查询的结果会被缓存,下次访问时直接从缓存获取
- 二级缓存开启的条件
- 配置文件中设置属性cacheEnabled="true"(默认为true)
- 映射文件最前端设置
- eviction: 缓存回收策略。LRU(最近最少使用,默认) | FIFO(先进先出) | SOFT(软引用) | WEAK(弱引用)
- flushInterval: 刷新间隔(毫秒)。默认不设置,仅在调用语句时刷新
- size: 引用数目。设置最大的缓存对象数
- readOnly: 只读。设置为true时对相同的查询返回同一个实例,因此是高性能的。设置为false返回的是缓存对象的拷贝。默认为false
- type: 设置二级缓存的实现方式。不配置则使用mybatis原生的二级缓存
- 二级缓存必须在Sqlsession关闭或提交后有效(没有关闭或提交时保存在一级缓存中)
- 查询结果所转换的实体类必须实现序列化的接口(Seri)
- 二级缓存失效情况
- 两次查询之间发生了增删改
- 缓存命中率: 从缓存中查询的次数在总查询次数中的占比
- 缓存查询的顺序:
- 优先查询二级缓存,找不到则查询一级缓存,依然没有找到则查询数据库
- SqlSession关闭后,一级缓存的数据会写入二级缓存
- 逆向工程旨在通过数据表生成对应的mapper、实体类、mapper.xml
- 逆向工程会生成相应的Example类,在其中提供了针对每个字段的条件。通过new Example()创建条件并提供给selectByExample方法即可实现条件查询
EmployeeExample example = new EmployeeExample(); example.createCriteria().andSalaryBetween(3000.0, 3200.0); example.or().andSalaryBetween(6000.0, 6200.0); List<Employee> employees = mapper.selectByExample(example);
- xxSelective方法与对应的非Selective方法的区别在于,前者将会避免修改类属性为null的字段,而后者将会把对应字段设置为null
- 依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
- 使用
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Page<Object> page = PageHelper.startPage(1, 5); // 开启分页(当前页数,页面条目数) List<Employee> employees = mapper.selectByExample(null); PageInfo<Employee> pageInfo = new PageInfo<>(employees, 7); // 获取分页信息(分页数据,当前导航分页的数量)
- pageInfo提供了众多属性,能够极大地简化浏览器页面中超链接的设置