1.占位符问题
mybatis3.4.1 之前版本版本,可以使用数字来占位
@Select("select * from t_user where user_name = #{0} and user_age = #{1}")
User getByNameAndAge(String userName,String age);
mybatis3.4.2 版本就换为 #{param1} #{param2} #{paramn..} 来映射 或者 使用参数名称,需要使用 @param 进行映射。使用 @param("name") 才可以使用 #{name} 单个参数
Typehandle getWithId(@Param("id") Integer id);
MyBatis 中使用 # 和 $ 书写占位符有什么区别? # 将传入的数据都当成一个字符串,会对传入的数据自动加上引号;会预编译。sql 安全不会注入。
$ 将传入的数据直接显示生成在 SQL 中。 注意:使用 $ 占位符可能会导致 SQL 注入攻击,能用#的地方就不要使用 $,写 order by 子句的时候应该用$而不是 #。
2.驼峰问题
springboot 配置可以直接加配置
mybatis.configuration.map-underscore-to-camel-case=true
但是如果有整合 mybtais-config.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>
<!-- 配置mybatis的缓存,延迟加载等等一系列属性 -->
<settings>
<!--启用log4j日志-->
<setting name="logImpl" value="LOG4J"/>
<!-- 全局映射器启用缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 允许使用列标签代替列名 -->
<setting name="useColumnLabel" value="true"/>
<!-- 给予被嵌套的resultMap以字段-属性的映射支持 -->
<setting name="autoMappingBehavior" value="FULL"/>
<!-- 对于批量更新操作缓存SQL以提高性能 -->
<setting name="defaultExecutorType" value="REUSE"/>
<!-- 数据库超过25000秒仍未响应则超时 -->
<setting name="defaultStatementTimeout" value="25000"/>
<!-- 自动转换驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Typehandle" type="com.entity.Typehandle"/>
</typeAliases>
</configuration>
3.mybatis结果集映射
注意:前者有构造方法的,对应实体类要实现构造方法,并且顺序
和构造方法参数
一致。如果使用lombok
要加上 @AllArgsConstructor
PS:同时使用 @Data 和 @AllArgsConstructor 后 ,默认的无参构造函数失效,如果需要它,要重新设置 @NoArgsConstructor
4.插入获取主键 ID
无事务控制
xml
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id"> insert into t_user(user_name,user_age) values(#{userName},#{age}) </insert>
注解形式
@Insert("insert into Demo(name) values(#{name})") @Options(keyProperty="id",keyColumn="id",useGeneratedKeys=true) public void save(Demo demo);
获取方式,注解 ID 会注入到入参对象中,可以直接 get 方法获取。
有事务控制
如果有事务控制如 @Transaction
无法通过以上方式获取,要先查询出来下个数据库自增id
。
select max(id)+1 from table;
5.嵌套 Select 查询
一般我们使用都是单表操作,多表都是手写 sql 进行关联查询,进行映射。这些都是一对一的情景。 一对一中,我们定义实体类型如果是复杂类型。
首先是订单类:
public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; //getter、setter }
用户信息类:
public class User { private int id; private String username; private int sex; private Date birthday; private String address; //getter、setter }
构建 resultMap 的时候,可以指定关联 association 的配置
<resultMap type="com.ssm.mybatis.po.Orders" id="selectOrderUserLazyLoading"> <!-- 配置映射的订单信息 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 配置映射的用户信息 --> <association property="user" javaType="com.ssm.mybatis.po.User" select="com.ssm.mybatis.mapper.UserMapper.findUserById" column="user_id" fetchType="lazy"> </association> </resultMap> <select id="selectOrderUserLazyLoading" resultMap="OrderUserLazyLoadingResultMap" > select * from orders </select>
注意:如果两表联查,主表和明细表的主键都是id的话,明细表的多条只能查询出来第一条。
解决方案:级联查询的时候,主表和从表有一样的字段名的时候,在 mysql 上命令查询是没问题的。但在 mybatis 中主从表需要为相同字段名设置别名。设置了别名
就 OK 了。
6.嵌套结果映射
使用嵌套的结果映射来处理连接结果的重复子集。一个复杂的数据结构包含集合的情况,恶心点的做法是先查出上层数据,再根据 id 迭代获取子结果注入到结果集中。这里使用高级映射来实现。
entity
public class Department { private Integer id; private String name; private List<Employee> employees; }
Mapper
public Department getDepartmentByIdPlus(Integer id);
XML
<!-- 嵌套结果集的方式--> <!--public Department getDepartmentByIdPlus(Integer id);--> <resultMap id="myDept" type="com.stayreal.mybatis.Department"> <id column="did" property="id"/> <result column="dept_name" property="name"/> <!-- collection定义关联集合类型的属性封装规则 offType:指定集合中的元素类型 --> <collection property="employees" ofType="com.stayreal.mybatis.Employee"> <id column="eid" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> </collection> </resultMap> <select id="getDepartmentByIdPlus" resultMap="myDept"> select d.id did,d.dept_name dept_name,e.id eid,e.last_name last_name, e.email email,e.gender gender from tbl_dept d left JOIN tbl_employee e on d.id = e.d_id where d.id = #{id} </select>
7.记录时间问题
mysql 对应datetime
类型,使用 now()
或者 sysdate()
代码中最好不要直接set时间。不要在代码中去生成时间(代码生成的时间是服务器系统时间,因为微服务会存在于不同服务器上,千万时间不一致)。插入时created_time = sysdate(), 更新时 update_time 不可更改 update_time = sysdate()
8.批量插入数据
for 循环
batch 插入 批处理需要在 JDBC url 添加参数 rewriteBatchedStatements=true
ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情。它为每个语句的执行创建一个新的预处理语句。 ExecutorType.REUSE: 这个执行器类型会复用预处理语句。 ExecutorType.BATCH:这个执行器会批量执行所有更新语句,如果 SELECT 在它们中间执行还会标定它们是 必须的,来保证一个简单并易于理解的行为。
foreach xml 循环
<!--批量添加--> <insert id="insertStandardItemInfo" parameterType="hashmap"> insert into t_standard_item(standard_id,item_id) values <foreach collection="itemIdList" index="index" item="item" separator="," > (#{standardId,jdbcType=INTEGER}, #{item,jdbcType=INTEGER}) </foreach> </insert>
ps:mysql 默认接受sql的大小是 1048576(1M),即第三种方式若数据量超过 1M 会报如下异常:(可通过调整MySQL 安装目录下的my.ini文件中[mysqld]段的"max_allowed_packet = 1M")
批量插入,外面再控制集合大小,分批次批量插入即可。可以使用 guava 现成的拆分 list 工具
// 按大小 2 拆分 Lists.partition(list, 2)