admin管理员组文章数量:1794759
【java学习】MyBatis使用——Java 数据持久层框架
1,概念
MyBatis是一个数据持久层(ORM)框架,封装了jdbc。把实体类和SQL语句之间建立了映射关系,是一种半自动化的ORM实现。MyBATIS需要开发人员自己来写sql语句,这可以增加了程序的灵活性,在一定程度上可以作为ORM的一种补充。 是由Apache开源项目iBatis3迁移到Github,命名为MyBatis。
1)优点ORM(Object Relation Mapping) O :object 对象 R: relation 关系 M: mapping 映射 ORM的作用就是把类转成SQL语句、可以把SQL语句转成类
使用<![CDATA[]]>来包含不被xml解析器解析的内容:”<”和”&”。但要注意的是: (1) 此部分不能再包含”]]>”; (2) 不允许嵌套使用; (3)”]]>”这部分不能包含空格或者换行。
比如<![CDATA[<]]> 表示文本内容“<” 2,使用mybatis的sql操作有三种:
配置连接、事务方式等。
3)po @TableName(value = "tbl_user", autoResultMap = true) public class User{ ... } 4)Mapper及常见注解 import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<User> { }关于sql操作,可以使用注解,也可以直接在xml中写sql,通过xml中标签的id来绑定。 对于简单sql可以直接使用mapper的包装类,对于复杂sql写xml中更灵活,所以不推荐使用注解进行开发。 org.apache.ibatis.annotations包常见注解见后文。
5)映射文件xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis//DTD Mapper 3.0//EN" "mybatis/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.test.mapper.postgresql.UserMapper "> --路径,必须和类名完全一致 各种标签,对应mapper类的各种方法。注意: 1.id要和mapper接口中的方法名完全一致 </mapper> 1>添加(insert标签) <!--int insertUser();--> <insert id="insertUser"> insert into t_user values(null,'admin','男') </insert> 2>删除(delete标签) <!--int deleteUser();--> <delete id="deleteUser"> delete from t_user where id = 7 </delete> 3>修改(update 标签) <!--int updateUser();--> <update id="updateUser"> update t_user set username='abc' where id = 7 </update> <update> update user <set> <if test="username != null">username=#{username}</if> </set> </update> set元素会前置set关键字,同时也会删除无关的逗号 等价于: <trim prefix="set" suffixoverrides=","> ... </trim> 4>查询对象(select 标签) <!--User getUserById();--> <select id="getUserById" resultType="com.luo.bean.User"> select* from t_user where id =7 </select >查询必须设置返回值: resultType(默认映射关系)、 resultMap(自定义映射关系、一对多多对一映射关系)。 查询集合:
<!--List<User> getAllUser();--> <select id="getAllUser" resultType="com.luo.bean.User"> select* from t_user </select > 5>foreach标签批量操作,如批量增删改。sql会自动拼在缓存中,一次性提交。
批量更新或插入:
<insert id="createPartitionTable" parameterType="java.util.ArrayList" > <foreach collection="list" item="item" index="index" separator=";"> CREATE TABLE if not EXISTS user_${item.sDate} PARTITION OF user FOR VALUES FROM ( '${item.sDatetime}' ) TO ( '${item.eDatetime}' ) </foreach> </insert> <insert id="insertBatch" parameterType="java.util.List"> INSERT INTO t_user (id, name, password) VALUES <foreach collection ="userList" item="user" separator =","> (#{user.id}, #{user.name}, #{user.password}) </foreach > </insert> 7>sql标签 <sql id="sql-1"> ... </sql> 其他标签可以直接引用sql片段: <...> <include refid="sql-1"></include> </...> 8>if标签对传入的参数处理后让下文可以使用新的变量。
<if test="userName != null and userName != ''"> <bind name="nameLike" value="'%' + userName + '%'"/> and user_name like #{nameLike} </if> 13>函数使用 <insert id="testFun"> DO $$ DECLARE num INTEGER=0; BEGIN select count(*) into num as num from (select distinct cardnum from bus_data) as A; insert into ic_test select num, dt from bus_data limit 100; END $$; </insert> 6)QueryWrapper继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件 及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取。
1>常见查询操作eq | 等于 | wrapper.eq("last_name", "皮皮虾"); |
ne | 不等于 | |
gt | 大于 | |
ge | 大于等于 | |
lt | 小于 | |
le | 小于等于 | |
like | ’%值%’ | |
notLike | ‘%值%’ | |
likeLeft | ‘%值’ | |
likeRight | ‘值%’ | |
between | 在值1和值2之间 | betweenWrapper.between("age", 10, 20); |
notBetween | 不在值1和值2之间 | |
isNull | 字段 IS NULL | isNullWrapper.isNull("email"); |
isNotNull | 字段 IS NOT NULL | |
in | 字段 IN (v0, v1, …) | inWrapper.in("age", 8, 16, 26); queryWrapper.in("type",typeList) |
notIn | 字段 NOT IN (value.get(0), value.get(1), …) | |
or | 或者 | orWrapper.gt(“age”, 20).or().eq(“gender”, 1); |
and | 和 | |
orderByAsc | 升序:ORDER BY 字段, … ASC | Wrapper.orderByAsc("id"); |
orderByDesc | 降序:ORDER BY 字段, … DESC | |
inSql | 字段 IN ( sql语句 ) | inSqlWrapper .inSql("select id from employee where id < 10"); |
notInSql | 字段 NOT IN ( sql语句 ) | |
exists | 拼接 EXISTS ( sql语句 ) | existsWrapper.exists(“select last_name,gender from employee where id = 1”); |
notExists | 拼接 NOT EXISTS ( sql语句 ) |
使用wrapper结合sql一起使用来进行查询:
缓存分为一级缓存和二级缓存,默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称本地缓存) 二级缓存需要手动开启和配置,是基于namespace级别的缓存 为了提高扩展性,MyBatis定义了缓存接口Cache,实现Cache可以自定义二级缓存
1>一级缓存仅仅对一个会话中的数据进行缓存(SqlSession): 映射语句文件(mapper.xml)中所有的select语句的结果将会被缓存,insert、udpate和delete会刷新缓存 缓存会使用最近最少使用算法刷新(LRU least Recently Used)清除不需要的缓存 缓存不会定时进行刷新(也就是说,没有刷新间隔) 缓存会保存列表和对象(无论查询方法返回哪一种)的1024个引用 缓存会被视为读/写缓存,这意味着获取到的对象不是共享的,可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改 缓存失效情况: 1、查询不同 2、增删改操作 3、查询不使用mapper.xml 4、手动清除缓存sqlSession.clearCache();
2>二级缓存要启用全局的二级缓存,只需要在SQL映射文件(mapper.xml)中添加即可 注意:二级缓存只作用于cache标签所在的mapper.xml文件的语句中。 如果使用JavaAPI和xml混用,在共同接口的语句将不会被默认缓存,需要使用@CacheNamespaceRef指定缓存作用区域。
<setting name="CacheEnable" value="true"/> <cache 清除策略:LRU、FIFO、SOFT、WEAK eviction="FIFO" 刷新间隔 flushInterval="60000" 引用数目 size="512" 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这就提升了可观的性能 而可读写的缓存就会通过序列化返回缓存对象的拷贝,速度上会慢一些,但是更安全,因此默认值是false readOnly="true"/> 8)分页 1>limit语句 <select id="..." parameterType="map"> ... limit #{size} offset (#{page}-1)*#{size} </select>注意: 如果返回值是Page<>,因为查询了total,检索效率会有影响。直接通过limit查速度就会快很多。
2>Wrapper的Page参数 //注意:page从1开始 Page<TTemplateData> page = new Page<>(condition.getPage(), condition.getPageSize()); IPage<TTemplateData> templateIPage = tTemplateDataMapper.selectPage(page, templateWrapper); 3>xml的Page参数 Page<TLabel> page = new Page<>(condition.getPage(), condition.getPageSize()); IPage<TLabel> labelIPage = tLabelMapper.selectPageList(page,keys,condition.getLabelTypeName()); IPage<TLabel> selectPageList(@Param("page") Page<TLabel> page,@Param("labelName") String labelName,@Param("labelTypeName") String labelTypeName); <select id = "selectPageList" resultMap="BaseResultMap"> select l.*,t.label_type_name labelTypeName from t_label l left join t_label_type t on l.label_type_id = t.id and t.status = 0 where l.status = 0 <if test="labelName != null and labelName != ''"> and l.label_name like concat('%',#{labelName},'%') </if> <if test="labelTypeName != null and labelTypeName != ''"> and t.label_type_name = #{labelTypeName} </if> </select> 3,MyBatis-plus 1)介绍MyBatis-plus简单来说就是一款 Mybatis 增强工具,用于简化开发,提高效率。
在我们使用Mybatis时会发现,每当要写一个业务逻辑的时候都要在DAO层写一个方法,再对应一个SQL,即使是简单的条件查询、即使仅仅改变了一个条件都要在DAO层新增一个方法。MybatisPlus这个框架上手快、不用写sql语句和xml文件。直接创建查询类,就可以迅速连接数据库了。无阿点是:MybatisPlus这种过度封装的框架,强行把service层,dao层,entity层绑定在一起,耦合度太高了。
2)常用注解见后文。
3)使用Mybatis-Plus通过EntityWrapper(简称EW,MP封装的一个查询条件构造器)或者条件(与EW类似)来让用户自由的构建查询条件,简单便捷,没有额外的负担,能够有效提高开发效率。
public interface UserMapper extends BaseMapper<User> { } 基本CRUD: //插入:成功会自动回写主键到实体类 result = userMapper.insert(user); 也可以直接:user.insert(); //更新: result = userMapper.updateById(user); 也可以直接:result = user.updateById(); //删除 result = userMapper.deleteById(user.getId()); 也可以直接:result = t2.deleteById(); //查询 User exampleUser = userMapper.selectById(user.getId());或者直接:User exampleUser = t1.selectById(); //批量查询:查询姓名为‘张三’的所有用户记录 List<User> userList = userMapper.selectList( new EntityWrapper<User>().eq("name", "张三") ); 或者直接:List<User> userList1 = user.selectList(...); //分页查询: 10 条姓名为‘张三’的用户记录 List<User> userList = userMapper.selectPage( new Page<User>(1, 10), new EntityWrapper<User>().eq("name", "张三") );或者直接:user.selectPage(...); // 分页查询 10 条姓名为‘张三’、性别为男,且年龄在18至50之间的用户记录 List<User> userList = userMapper.selectPage( new Page<User>(1, 10), new EntityWrapper<User>().eq("name", "张三") .eq("sex", 0) .between("age", "18", "50") ); 4,原理 5,常用注解 1)类注解 1>@Mapper在mapper接口使用@Mapper注解,编译之后会生成相应的接口实现类。xml文件可以不写。
import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<User> { }使用 @Mapper,最终 Mybatis 会有一个拦截器,会自动的把 @Mapper 注解的接口生成动态代理类。
2>@MapperScan每个接口可以不写@Mapper注解,统一在启动类上用@MapperScan注解。@MapperScan 配置一个或多个包路径,自动的扫描这些包路径下的类,自动的为它们生成代理类。
import org.mybatis.spring.annotation.MapperScan; @SpringBootApplication //直接注入某个类 @MapperScan(basePackageClasses = com.lwh.mapper.UserMapper.class) //直接注入某个路径 @MapperScan({"com.xttblog.mapper","com.xttblog.dao"}) @MapperScan(value = {"com.fanr.graduation.mapper"}) @ComponentScan(basePackages = {"com.xttblog.*"})当使用了 @MapperScan 注解,将会生成 MapperFactoryBean, 如果没有标注 @MapperScan 也就是没有 MapperFactoryBean 的实例,就走 @Import 里面的配置,具体可以在 AutoConfiguredMapperScannerRegistrar 和 MybatisAutoConfiguration 类中查看源代码进行分析。
2)PO注解 1>@TableName实体类对应表名,注解在类上。
import com.baomidou.mybatisplus.annotation.TableName; //autoResultMap = true表示:xml 字段映射 resultMap ID @TableName(value = "tbl_user", autoResultMap = true) // 注解指定表名 public class User extends Model<User> { } 2>@TableId主键字段,注解在属性上。 通过value字段写不同的命名方式(可省略);通过type设置自增算法(默认雪花算法)。
import com.baomidou.mybatisplus.annotation.TableId; @TableId(value="uid",type=IdType.AUTO) private long id;IdType.AUTO | 数据库ID自增 |
IdType.NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
IdType.INPUT | insert前自行set主键值 |
IdType.ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) |
IdType.ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法) |
除了通过type属性值来配置主键的规则外,还可以通过application.yml文件来配置:
mybatis-plus: global-config: db-config ##注解设置 id-type:auto ##对于没有值的数据自动赋予空,设置为false可能查不出来导致返回字段不全的问题。 call-setters-on-nulls: true 3>@TableField设置字段属性,通过value命名(value=可以省略),默认驼峰命名法。
//实体临时字段 @TableField(exist=false) import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; import org.apache.ibatis.type.JdbcType; //fill为自动填充策略, //FieldFill: DEFAULT:默认不处理;INSERT:插⼊时填充字段;UPDATE:更新时填充字段;INSERT_UPDATE:插⼊和更新时填充字 @TableField(jdbcType = JdbcType.TIMESTAMP, fill = FieldFill.INSERT) private Date timestamp;//pg中为timestamp类型 @TableField(value = "category_name", jdbcType = JdbcType.VARCHAR) private String categoryName; @TableField(jdbcType = JdbcType.INTEGER) private Integer times;//pg中对应为int4 @TableField(value = "user_info", typeHandler = FastjsonTypeHandler.class) private List<JSONObject> userList; @TableField(jdbcType = JdbcType.VARCHAR, typeHandler = JsonbTypeHandler.class) private Map<String, String> map = Collections.EMPTY_MAP;//pg中对应为jsonb @TableField(jdbcType = JdbcType.VARCHAR,typeHandler = JsonListTypeHandler.class) private List<String> list = new ArrayList<>();//pg中对应为jsonb @TableField(jdbcType = JdbcType.BIGINT) private Long time = System.currentTimeMillis();//pg中对应为int8 @TableField(jdbcType=JdbcType.BOOLEAN) private Boolean state; @TableField(jdbcType = JdbcType.ARRAY) private int[] arr= new int[0];//pg中对应int4[] @Type(type = "DisplayAndJumpLinkJsonType") private DisplayAndJumpLink displayAndJumpLink;自定义映射处理类:JsonListTypeHandler
//注意,此处用泛型,如果传值过小,会用List<Integer>接数据,用List<Long>会报错,可以用Number接数据,也可以重新写一个TypeHandler(继承BaseTypeHandler<List<Long>>,重写getNullableResult方法:result = value == null ? null : JSON.parseObject(value, new TypeReference<List<Long>>(){});) public class JsonListTypeHandler extends BaseTypeHandler<List<?>> { @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<?> objects, JdbcType jdbcType) throws SQLException { if (objects == null) { try { preparedStatement.setNull(i, JdbcType.OTHER.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { try { preparedStatement.setObject(i, FastJsonUtil.toJSON(objects), JdbcType.OTHER.TYPE_CODE); } catch (Exception e) { throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + e, e); } } } @Override public List<?> getNullableResult(ResultSet resultSet, String s) throws SQLException { List<?> result; try { String value = resultSet.getString(s); result = value == null ? null : FastJsonUtil.parse(value, List.class); } catch (Exception e) { throw new ResultMapException( "Error attempting to get column '" + s + "' from result list. Cause: " + e, e); } if (resultSet.wasNull()) { return new ArrayList<>(); } else { return result; } } @Override public List<?> getNullableResult(ResultSet resultSet, int i) throws SQLException { List<?> result; try { String value = resultSet.getString(i); result = value == null ? null : FastJsonUtil.parse(value, List.class); } catch (Exception e) { throw new ResultMapException( "Error attempting to get column #" + i + " from result list. Cause: " + e, e); } if (resultSet.wasNull()) { return new ArrayList<>(); } else { return result; } } @Override public List<?> getNullableResult(CallableStatement callableStatement, int i) throws SQLException { List<?> result; try { String value = callableStatement.getString(i); result = value == null ? null : FastJsonUtil.parse(value, List.class); } catch (Exception e) { throw new ResultMapException( "Error attempting to get column #" + i + " from callable statement. Cause: " + e, e); } if (callableStatement.wasNull()) { return new ArrayList<>(); } else { return result; } } }value | 字段值(驼峰命名方式,该值可无) |
el | 是否为数据库表字段( 默认 true 存在,false 不存在 ) |
exist | 是否为数据库表字段( 默认 true 存在,false 不存在 ) |
strategy | 字段验证 ( 默认 非 null 判断,查看 com.baomidou.mybatisplus.enums.FieldStrategy ) |
fill | 字段填充标记 ( 配合自动填充使用 ) |
CHAR | String |
VARCHAR | String |
LONGVARCHAR | String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
BOOLEAN | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
CLOB | Clob |
BLOB | Blob |
ARRAY | Array |
DISTINCT | mapping of underlying type |
STRUCT | Struct |
REF | Ref |
DATALINK | java.URL[color=red][/color] |
逻辑删除,在属性上命名。
sql中参数的两种方式: ${} 本质是字符串拼接,容易引起sql注入,不推荐使用。 #{} 本质是占位符赋值
$转换#的方式:
name like concat('%',#{keyword,jdbcType=VARCHAR},'%') <if test="ids != null"> and id in <foreach collection="ids" item="item" open="(" separator="," close=")"> #{item} </foreach> </if> 2>@Select import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface UserMapper extends BaseMapper<User> { //1. 可以直接使用 `@Select` 注解。 //2. xml中可以使用select标签进行绑定与映射,id一定要与方法名(getUserById)保持一致。 @Select("select* from t_user where id =7") User getUserById(@Param("id")Long id); } 3>@Options设置缓存时间,能够为对象生成自增的key。
@Insert("INSERT INTO city (name, state, country) VALUES(#{name}, #{state}, #{country})") @Options(useGeneratedKeys = true, keyProperty = "id") void insert(City city); --useGenerateKey=true : 设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中 --useCache = true表示本次查询结果被缓存以提高下次查询速度, --flushCache = false表示下次查询时不刷新缓存, --timeout = 10000表示查询结果缓存10000秒。 4>@Insert我希望通过dao层的接口插入的数据能够返回主键的id(最终插入成功后从instance_id这个字段里面把数据放到传入对象的instanceId成员变量里面):
@Insert("insert into instance (infos)" + " (" + " @{infos}," + " NOW()" + ")") @Options(useGeneratedKeys = true, keyProperty = "instanceId", keyColumn = "instance_id") int addInstance(Instance instance); 5>@Update @Update("sql") public void createIfNotExistsTable(); 6>@Delete @Delete("delete from t_user where id=#{id}") int deleteUser(@Param("id") Integer id); 7>@ResultType指定返回类型
@Select(value = "SELECT s.ID,s.CATEGORY_NAME AS name, s.PARENT_ID,p.CATEGORY_NAME AS parent_name FROM cd_category s LEFT JOIN cd_category p ON p.ID=s.PARENT_ID where FIND_IN_SET(s.id, query_children_category(${id}))") @ResultType(ItemCategoryModel.class) public List<ItemCategoryModel> getItemCategoryTree(@Param(value = "id") Integer id); @Select("select role_id AS roleId from user_role where user_id = #{userId}") @ResultType(Long.class) List<Long> selectRoleIdListByUserId(@Param("userId") Long userId); 8>@Results和@Result当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来。column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。
@SelectProvider(type=SqltoolMetadataSqlProvider.class, method="selectByExample") @Results({ @Result(column="ID", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="NAME", property="name", jdbcType=JdbcType.VARCHAR) }) List<SqltoolMetadata> selectByExampleWithRowbounds(SqltoolMetadataCriteria example, RowBounds rowBounds); 9>@ResultMap当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER) }) List<Student> selectAll(); @Select({"select id, name, class_id from my_student where id = #{id}"}) @ResultMap(value="studentMap") Student selectById(integer id); 10>@One当我们需要通过查询到的一个字段值作为参数,去执行另外一个方法来查询关联的内容,而且两者是一对一关系时,可以使用@One注解来便捷的实现。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="myClass", javaType=MyClass.class, one=@One(select="com.example.demo.mapper.MyClassMapper.selectById")) }) List<Student> selectAllAndClassMsg(); 11>@Many与@One类似,只不过如果使用@One查询到的结果是多行,会抛出TooManyResultException异常,这种时候应该使用的是@Many注解,实现一对多的查询。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER), @Result(column="id", property="gradeList", javaType=List.class, many=@Many(select="com.example.demo.mapper.GradeMapper.selectByStudentId")) }) List<Student> selectAllAndGrade(); 12>@SelectProvider用自定义的provider类构造SQL语句,sql语句由type指定的类中的method方法返回。
// mapper类: @SelectProvider(type = SalesOrderProvider.class, method = "selectSalesInformation") List<SalesInformation> selectSalesInformation(@Param("createDateStart") String createDateStart, @Param("createDateEnd") String createDateEnd); //SalesOrderProvider类 public String selectSalesInformation(@Param("createDateStart") String createDateStart, @Param("createDateEnd") String createDateEnd){ StringBuffer sql = new StringBuffer(); sql.append(" SELECT * from user"); return sql.toString(); } 13>@InsertProvider批量新增:
@InsertProvider(type = BatchPreparationMsg.class, method = "batchAdd") Integer batStuAdd(@Param("list") List<PreparationMsg> list); //BatchPreparationMsg类: public String batchAdd(@Param("list") List<TaskTemplate> list){ StringBuilder sb = new StringBuilder(); sb.append("insert into pep_preparation_msg(REF_TABLE,REF_ID,HOTEL_ID,TASK_ID,TYPE,NAME,CONTENT,USERGROUP,SHOW_FLAG,CREATE_USER,CREATE_TIME) values"); MessageFormat mf = new MessageFormat( "(#'{'list[{0}].refTable},#'{'list[{0}].refId},#'{'list[{0}].hotelId},#'{'list[{0}].taskId},#'{'list[{0}].type},#'{'list[{0}].name}," + "#'{'list[{0}].content},#'{'list[{0}].usergroup},#'{'list[{0}].showFlag},#'{'list[{0}].createUser},#'{'list[{0}].createTime})" ); for (int i = 0; i < list.size(); i++) { sb.append(mf.format(new Object[]{i})); if (i < list.size() - 1) { sb.append(","); } } return sb.toString(); } 14>@UpdateProvider类似@InsertProvider,批量更新。
15>@DeleteProvider类似@InsertProvider,批量删除。
16>@SelectKey类似标签<selectKey>
@SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="clusterId", before=false, resultType=Integer.class) - statement属性:填入将会被执行的 SQL 字符串数组。 - keyProperty属性:填入将会被更新的参数对象的属性的值。 - before属性:填入 true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行。 - resultType属性:填入 keyProperty 的 Java 类型。 - statementType属性:填入Statement、 PreparedStatement 和 CallableStatement 中的 STATEMENT、 PREPARED 或 CALLABLE 中任一值填入 。默认值是 PREPARED。注意: @SelectKey注解用在已经被 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 注解了的方法上。若在未被上述四个注解的方法上作 @SelectKey 注解则视为无效。
如果向数据库中插入一条数据,同时有希望返回该条记录的主键,该怎么处理了?有两种情况: (1)数据库主键不是自增列,需要预先生成 (2)是自增列,插入之后才能获知 这两种情况都可以通过SelectKey解决,第一个种就是before,第二张是after。
17>@MapKeymybatis返回map类型,希望多条数据放到Map<K,T>中而不是List<Map<K,T>>。
@Select("select id, name from users") @MapKey("id") Map<Integer, User<String>> getAMapOfUsers(); 18>@ConstructorArgs根据构造方法构造对象返回。
@Select("select id, name from product where name = #{value}") @ConstructorArgs({ @Arg(id = true, column="id", javaType = ProductId.class, jdbcType=JdbcType.INTEGER), @Arg(column="name") }) Product getProductByNameUsingConstructor(String name); 19>@CacheNamespaceRef和@CacheNamespaceCacheNamespace的引用,表示开启二级缓存,相当于<cache>标签。
<mapper namespace="cn.mybatis.mydemo.mapper.StudentMapper"> <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> </mapper> 当然,前提还需要在全局配置文件中开启缓存: <setting name="cacheEnabled" value="true"/> 或者这样使用: @CacheNamespaceRef(name="com.luo.test.UserMapper" public interface UserMapper extends BaseMapper<User> { } 6,原理 1)MyBatis工作原理 2)MyBatis插件 工作原理MyBatis 通过使用拦截器可以自定义增强MyBatis 的功能。MyBatis 通过JDK动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。每当执行这4种对象(ParameterHandler、ResultSetHandler、Executor、StatementHandler)的方法时,就会进入拦截方法(InvocationHandler的invoke()方法。
运行拦截的对象:
ParameterHandler | sql参数组装的过程 |
ResultSetHandler | 返回结果集的组装 |
Executor | 上层的对象,sql执行全过程,包括组装参数、组装结果集返回、执行SQL过程 |
StatementHandler | 执行SQL的过程,最常用的拦截对象 |
自定义插件实现:
/** * @Intercepts 注解标记这是一个拦截器,其中可以指定多个@Signature * @Signature 指定该拦截器拦截的是四大对象中的哪个方法 * type:拦截器的四大对象的类型 * method:拦截器的方法,方法名 * args:入参的类型,可以是多个,根据方法的参数指定,以此来区分方法的重载 */ @Intercepts( { @Signature(type = ParameterHandler.class,method ="setParameters",args = {PreparedStatement.class}) } ) public class ParameterInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("拦截器执行:"+invocation.getTarget()); //目标对象 Object target = invocation.getTarget(); //获取目标对象中所有属性的值,因为ParameterHandler使用的是DefaultParameterHandler,因此里面的所有的属性都封装在其中 MetaObject metaObject = SystemMetaObject.forObject(target); //使用xxx.xxx.xx的方式可以层层获取属性值,这里获取的是mappedStatement中的id值 String value = (String) metaObject.getValue("mappedStatement.id"); //如果是指定的查询方法 if ("cn.cb.demo.dao.UserMapper.selectByUserId".equals(value)){ //设置参数的值是admin_1,即是设置id=admin_1,因为这里只有一个参数,可以这么设置,如果有多个需要需要循环 metaObject.setValue("parameterObject", "admin_1"); } //执行目标方法 return invocation.proceed(); } @Override public Object plugin(Object target) { //如果没有特殊定制,直接使用Plugin这个工具类返回一个代理对象即可 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }注入Mybatis:
/** * @Configuration:这个注解标注该类是一个配置类 */ @Configuration public class MybatisConfig{ /** * @Bean : 该注解用于向容器中注入一个Bean * 注入Interceptor[]这个Bean * @return */ @Bean public Interceptor[] interceptors(){ //创建ParameterInterceptor这个插件 ParameterInterceptor parameterInterceptor = new ParameterInterceptor(); //放入数组返回 return new Interceptor[]{parameterInterceptor}; } }测试:
@Test void contextLoads() { //传入的是1222 UserInfo userInfo = userMapper.selectByUserId("1222"); System.out.println(userInfo); //测试代码传入的是1222,由于插件改变了入参,因此查询出来的应该是admin_1这个人。 }版权声明:本文标题:【java学习】MyBatis使用——Java 数据持久层框架 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686831301a107960.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论