Mybatis-Plus知识点详解
Mybatis-plus(简称MP),基于Mybatis的增强工具,保留了Mybatis的所有功能,同时增加了通用的CRUD,条件构造器,分页插件等等实用工具
特性
- 即拿即用:通过通用Mapper和Service,无需编写XML既可以完成单表CURE操作
- Lambda支持:使用Lambda表达式构建查询条件,避免硬编码字段名,提升代码安全性
- 更多的主键策略:支持多种主键生成方式,如雪花算法,自增等等
- 分页插件:内置分页插件,支持多种数据库,简化分页查询操作
与Mybatis相比
- 提供大量自动化功能,如通用的CRUD,条件构造器,分页支持极大减少操作代码,提高开发效率
- Mybatis需手动编写SQL,编写XML文件和映射接口DAO
快速入门
日志配置
- 日志输出到标准输出流中
mybatis-plus:
# mapper配置文件
mapper-locations: classpath:mapper/*.xml
# resultType别名,没有这个配置resultType包名要写全,配置后只要写类名
type-aliases-package: com.mashang.springboot04.domain
//
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
添加依赖与配置application.yml
- 本人使用
SpringBoot 2.7.18,JDK1.8
:导入的依赖如下
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
- application.yml如下:
# 也是springboot的配置文件,两个都可以用
server:
port: 8080
servlet:
#配置根路径
context-path: /
# 配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xiaomi_store?userSSL=false;serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
# mapper配置文件
mapper-locations: classpath:mapper/*.xml
# resultType别名,没有这个配置resultType包名要写全,配置后只要写类名
type-aliases-package: com.mashang.springboot03.domain
定义实体类与Mapper接口
实体类
- 在实体类上使用Mybatis-plus的注解
@TableName()
→指定数据库查询的表,不添加默认将类名改为下划线型数据库名@TableId(name,type)
→用于给主键标记,name为数据库对应名,用于指定自增算法- 雪花算法(Snowflake)是由Twitter开源的一种分布式ID生成算法.其核心思想是将64位的long型ID分为四个部分,分别为:符号位,时间戳,工作机器ID,序列号
@Data
@TableName("user") // 指定数据库表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String name;
private Integer age;
}
Mapper接口
- Mapper接口需继承
BaseMapper<T>
类,则此接口即可获得所有基本的CRUD方法
public interface UserMapper extends BaseMapper<User> {
// 如果需要扩展自定义方法,也可以在此添加
}
Service层与Service实现层
MP也提供了IService接口和ServiceImpl类方便在Service层使用封装好的CRUD方法
Service层
- extends(继承)IService接口
public interface UserService extends IService<User> {
// 可以在此定义更多业务方法
}
ServiceImpl层
- 继承
ServiceImpl<UserMapper, User>
和实现IService<User>
接口,可以直接调用封装好的 CRUD 方法
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 继承了 ServiceImpl 后,已拥有 BaseMapper 中所有的 CRUD 方法
}
BaseMapper接口中常用的CRUD方法
- 即Mapper层中的常见方法,已经集成好了,直接操作数据库的原子性方法
插入操作
// 插入一条记录
int insert(T entity);
查询操作
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
删除操作
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
更新操作
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
IService接口中的常用方法
- 即service层常见方法,业务逻辑层,可组合多个 Mapper 方法,添加事务等
插入操作(save)
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
存在更新,不存在插入(saveOrUpdate)
boolean saveOrUpdate(T entity);
:根据@TableId标识的主键判断,若主键不存在→执行INSERT
,若主键存在→执行UPDATE
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
:先根据updateWrapper尝试进行更新操作,若影响行数为0,则回退到saveOrUpdate(T entity)
逻辑(根据主键判断是否更新或插入)saveOrUpdateBatch(Collection<T>entityList)
和saveOrUpdateBatch(Collection<T> entityList, int batchSize)
:批量处理集合中的实体,逻与saveOrUpdate(T)
一致
删除操作(Remove)
可配合
@TableLogic
实现逻辑删除
// 根据 Wrapper 构造的条件删除记录 支持复杂条件
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
//根据 columnMap 中的字段值匹配删除记录 仅支持等值条件,
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
更新操作(Update)
必须设置
SET
字段,如果未调用.set()
,SQL会缺少更新内容导致错误
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
查询单个对象(getOne)
若实体类配置了
@TableLogic
,查询时会自动过滤已删除数deleted = 0
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录,结果集,如果是多个会抛出异常, 随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录,可控制是否在结果不唯一时抛出异常
//不抛出异常,返回第一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
查询列表(list)
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
分页查询(page)
分页对象Page类
- 主要属性:
current
:当前页码,从 1 开始size
:每页显示的记录数searchCount
:是否执行SELECT COUNT(*)
查询总记录数,不统计总数适用于大数据量场景
Page<User> pageParam = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 25)
.orderByDesc(User::getCreateTime); IPage<User> userPage = userService.page(pageParam, wrapper);
- 主要属性:
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
查询记录数(count)
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
// 统计年龄大于25且状态为激活的用户数量
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 25)
.eq(User::getStatus, 1);
int count = userService.count(wrapper);
条件构造器
- 条件构造器用于动态生成SQL中的WHERE条件,替代手动拼接SQL语句,MP中提供了两种主要的条件构造器:QueryWrapper与LambdaQueryWrapper
- QueryWrapper:基于字符串名的条件构造器,写法直观,但存在字段拼写错误的风险
- LambdaQuertWrapped:基于Lambda表达式的构造器,避免了硬编码字段,更加推荐使用
常用方法
等于.eq()
用于生成
字段=值
的条件- 使用字符串
new QueryWrapper<User>().eq("age", 25); // 生成 SQL: WHERE age = 25
- 使用Lambda表达式
new LambdaQueryWrapper<User>().eq(User::getAge, 25);
不等于.nq()
- 生成
字段<>值
条件
new LambdaQueryWrapper<User>().ne(User::getStatus, 0); // SQL: WHERE status <> 0
模糊查询.like()
生成
字段LIKE ‘%值%’
条件new LambdaQueryWrapper<User>().like(User::getName, "张"); // SQL: WHERE name LIKE '%张%'
逻辑连接or()与and()
- or():一般用法
new LambdaQueryWrapper<User>()
.eq(User::getAge, 25)
.or()
.eq(User::getName, "张三");
// 生成 SQL: WHERE age = 25 OR name = '张三'
- 嵌套用法
new LambdaQueryWrapper<User>()
.or(wrapper -> wrapper
.eq(User::getAge, 25)
.ne(User::getStatus, 0)
);
// 生成 SQL: OR (age = 25 AND status <> 0)
- and():作用是拼接多个条件,用 AND 连接默认条件连接就是 AND,一般无需显式调用
动态查询
- 在查询条件前加上布尔判断,true则执行
new LambdaQueryWrapper<User>()
.eq(age != null, User::getAge, age) // 当 age 不为 null 时生效
.like(StringUtils.isNotBlank(name), User::getName, name); // 当 name 非空时生效
分页查询
MP中内置了分页插件,使用Page对象来封装分页参数和结果
在Springboot中通过配置类启动分页插件
- 启用 MyBatis-Plus 的分页功能,使得在查询时可以直接使用分页方法
@Configuration
public class MyBatisPlusConfig {
// MybatisPlus的配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 在拦截器中加入了一个分页的拦截器
// 指定数据库类型为 MySQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20);
Page<User> pageParam = new Page<>(1, 10);
IPage<User> userPage = userService.page(pageParam, wrapper);
进阶操作
敏感字段屏蔽
- 某些字段直接发给前端会被用户恶意使用,导致安全隐患.有些字段(如日志字段,update_time,内部状态等等)对前端并无必要,过滤这些可以减少网络数据量,提高性能
方案一:手动赋值
- 手动创建VO对象,并逐字赋值
List<StudentVo> studentVos = new ArrayList<>();
for (Student s : students) {
StudentVo vo = new StudentVo();
vo.setName(s.getName());
vo.setClassId(s.getClassId());
// 其它字段也需手动赋值
studentVos.add(vo);
}
- 优点:可控,不存在反射性能开销
- 缺点:当字段过多时,代码冗长,维护成本高,若实体类改变,需要改变多个地方
方案二:BeanUtils.copyProperties
- 实现方法:利用反射机制自动复制同名属性
List<StudentVo> studentVos = new ArrayList<>();
for (Student s : students) {
StudentVo vo = new StudentVo();
BeanUtils.copyProperties(student, vo);
studentVos.add(vo);
}
- 优点:代码简洁,快速复制
- 缺点:反射操作性能慢,约为手动复制的10倍,缺乏灵活性
方案三:MapStruct 推荐
导入MapStruct依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
建一个专门用于映射的文件夹mapping,编写映射接口,利用MapStruct在编译阶段自动生成高效映射代码
@Mapper//注意mapper注解不要引错了,要引入mapstruct包下的
public interface StudentMapping {
StudentMapping INSTANCE = Mappers.getMapper(StudentMapping.class);
StudentVo toStudentVo(Student student);
List<StudentVo> toStudentVoList(List<Student> list);
}
//使用方法如下:
List<StudentVo> vos = StudentMapping.INSTANCE.toStudentVoList(students);
- 优点:性能高,生成的代码类似于手动赋值,运行时没有反射开销,灵活性高
联表分页查询
- 假设需要查询主表数据(如商品),及其关联的子表数据(如评论),支持分页和条件过滤.以商品(主表)和评论(子表)为例
集中式
核心逻辑是提供JOIN查询主表和子数据,在单次SQL中完成所有数据的获取
代码实例
- mapper.xml
<!-- 商品和评论联表查询 -->
<select id="selectProductWithComments" resultMap="productWithCommentsMap">
SELECT
p.id,
p.name,
p.price,
c.id AS comment_id,
c.content,
c.user_id
FROM product p
LEFT JOIN comment c ON p.id = c.product_id
${ew.customSqlSegment} <!-- 动态条件 -->
</select> <!-- 结果映射 -->
<resultMap id="productWithCommentsMap" type="ProductVO">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="price" column="price"/> <collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
<result property="content" column="content"/>
<result property="userId" column="user_id"/>
</collection>
</resultMap>
- 核心点:
使用处理一对多的关系,将子表数据映射为集合
${ew.customSqlSegment}
- 其是MP中用于动态拼接的特殊占位符,将
QueryWrapper
或LambdaQueryWrapper
中定义的查询条件如(eq,like,between
),转换为合法的Sql片段,并插入到XML映射文件的SQL语句中 - mapper接口定义
// 方法参数中必须声明 @Param(Constants.WRAPPER)
Page<ProductVO> selectPage(
@Param("page") Page<ProductVO> page,
@Param(Constants.WRAPPER) Wrapper<ProductVO> wrapper // 关键参数
);
- XML的使用
<select id="selectPage" resultMap="productMap">
SELECT * FROM product
${ew.customSqlSegment} <!-- 动态插入 WHERE 条件 -->
</select>
- 核心机制
- ew是Wrapper的别名,对应Mapper接口方法中的
@Param(Constans.WRAPPER)
参数 - 当调用
wrapper.eq(”name”,”张三”)
时,会自动赋值为WHERE name= ‘张三’如WHERE name = '手机'
需直接拼接,不能作为预编译参数
- ew是Wrapper的别名,对应Mapper接口方法中的
- 其是MP中用于动态拼接的特殊占位符,将
分布式(嵌套查询)
- 分为两次查询:1.分页查询主表()商品表,2.子查询:根据主表id批量查询关联的子表数据(评论表)
- 代码实例
<!-- 主查询 -->
<select id="selectProductPage" resultMap="productPageMap">
SELECT id, name, price FROM product
${ew.customSqlSegment} <!-- 动态条件 -->
</select>
<!-- 嵌套子查询 根据商品ID查评论 -->
<select id="selectCommentsByProductIds" resultType="Comment">
SELECT id, content, user_id, product_id
FROM comment
WHERE product_id IN
<foreach collection="productIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 结果映射 -->
<resultMap id="productPageMap" type="ProductVO">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
<collection
property="comments"
ofType="Comment"
select="selectCommentsByProductIds" <!-- 关联子查询方法 -->
column="{productIds=id}" <!-- 传递参数 -->
/>
</resultMap>
字段填充
- 再更新或者新增行时,通常需要添加一些属性如创建时间,更新时间,操作人员,MP提供了一种方法自动化处理填充
代码层面的实现
再实体类中标记需要填充的字段
- 使用
@TableFiled
主键的fill
属性指定填充策略
@Data
public class Orders implements Serializable {
private static final long serialVersionUID = -29854105041572661L; @TableId(type = IdType.ASSIGN_ID)
private Long orderId; private String orderNum; private Integer userId; @TableField(fill = FieldFill.INSERT)
private Date orderTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 备注
*/
private String remark; }
b. 实现MetaObjectHandler接口
@Slf4j
@Component
public class FieldHandler implements MetaObjectHandler {
// 在插入数据时,自动填充createTime和createBy字段
@Override
public void insertFill(MetaObject metaObject) { log.info("start insert fill...");
// 设置createTime字段的值为当前时间
this.setFieldValByName("createTime", new Date(), metaObject); // 设置createBy字段的值为admin
this.setFieldValByName("createBy", "admin", metaObject); // 设置orderTime字段的值为当前时间
this.setFieldValByName("orderTime", new Date(), metaObject); // 设置updateTime字段的值为当前时间
this.setFieldValByName("updateTime", new Date(), metaObject); // 设置updateBy字段的值为admin
this.setFieldValByName("updateBy", "admin", metaObject);
} // 在更新数据时,自动填充updateTime和updateBy字段
@Override
public void updateFill(MetaObject metaObject) { log.info("start update fill...");
// 设置updateTime字段的值为当前时间
this.setFieldValByName("updateTime", new Date(), metaObject); // 设置updateBy字段的值为admin
this.setFieldValByName("updateBy", "admin", metaObject);
}
}- 使用
逻辑删除
- 数据“删除”后仍保留在数据库中,通过标记is_deleted字段过滤,在实体类中使用
@TableLogic
注解 - 避免误删导致数据永久丢失,防止删除主表数据后,关联的子表数据失效(如用户删除后,历史订单需保留)
public class User {
@TableLogic
private Integer deleted; // 1 表示删除,0 表示未删除
}
- 当执行删除操作时,实际是在执行update操作
userMapper.deleteById(1);
//实际执行
UPDATE user SET is_deleted = 1 WHERE id = 1;
- 查询操作通过is_deteted过滤
List<User> list = userMapper.selectList(null);
//实际执行
SELECT * FROM user WHERE is_deleted = 0;
乐观锁
- 通过版本号(如Vesion字段)或时间戳,再数据提交时检查是否发生并非冲突,若冲突则拒绝操作,提示重试或回滚,先操作,提交时再检查冲突,使用于低并发
MP实现乐观锁
- 在实体类中添加版本号,并使用@Version注解标记
public class Product {
private Long id;
private String name;
@Version // 版本号字段(MP自动管理)
private Integer version;
}
- 在配置类中添加乐观锁插件
@EnableTransactionManagement
public class MPConfig {
// 创建MybatisPlusInterceptor对象
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
当存在多线程的修改操作时,MP会通过SQL语句检查version版本是否一致,如查询商品时version为1,提交完修改时MP执行
UPDATE product SET name='新名字', version=2 WHERE id=1 AND version=1;
,若存在另一个线程抢先修改,则version变为2,你的更新就会失效
Mybatis-Plus知识点详解的更多相关文章
- mybatis代码生成器配置文件详解
mybatis代码生成器配置文件详解 更多详见 http://generator.sturgeon.mopaas.com/index.html http://generator.sturgeon.mo ...
- 深入浅出mybatis之启动详解
深入浅出mybatis之启动详解 MyBatis功能丰富,但使用起来非常简单明了,今天我们来追踪一下它的启动过程. 目录 如何启动MyBatis 如何使用MyBatis MyBatis启动过程 如何启 ...
- MyBatis核心配置文件详解
------------------------siwuxie095 MyBatis 核心配置文件详解 1.核心 ...
- 《深入理解mybatis原理2》 Mybatis初始化机制详解
<深入理解mybatis原理> Mybatis初始化机制详解 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程 ...
- Mybatis案例超详解(上)
Mybatis案例超详解(上) 前言: 本来是想像之前一样继续跟新Mybatis,但由于种种原因,迟迟没有更新,快开学了,学了一个暑假,博客也更新了不少,我觉得我得缓缓,先整合一些案例练练,等我再成熟 ...
- Activity知识点详解
Activity知识点详解 一.什么是Activity 官方解释: The Activity class is a crucial component of an Android app, and t ...
- MyBatis Mapper XML 详解
MyBatis Mapper XML 详解 MyBatis 真正的力量是在映射语句中.这里是奇迹发生的地方.对于所有的力量,SQL 映射的 XML 文件是相当的简单.当然如果你将它们和对等功能的 JD ...
- Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节
简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...
- MyBatis 全局配置文件详解(七)
MyBatis 配置文件作用 MyBatis配置文件包含影响 MyBatis 框架正常使用的功能设置和属性信息.它的作用好比手机里的设置图标,点击这个图标就可以帮助我们查看手机的属性信息和设置功能.其 ...
- mybatis 学习笔记 -详解mybatis 及实例demo
快速入门1 要点: 首先明白mybatis 是什么 这是一个持久层的框架.之前叫做ibatis.所以,在它的代码中出现ibatis这个词的时候,不要感到惊讶.不是写错了,它确实就是这个样子的. 首先, ...
随机推荐
- 开源轻量级 IM 框架 MobileIMSDK v6.1.2 发布!
一.更新内容简介 本次更新为次要版本更新,进行了若干优化(更新历史详见:码云 Release Nodes).可能是市面上唯一同时支持 UDP+TCP+WebSocket 三种协议的同类开源IM框架. ...
- JVM实战—9.线上FGC的几种案例
大纲 1.如何优化每秒十万QPS的社交APP的JVM性能(增加S区大小 + 优化内存碎片) 2.如何对垂直电商APP后台系统的FGC进行深度优化(定制JVM参数模版) 3.不合理设置JVM参数可能导致 ...
- python实现excel数据处理
python xlrd读取excel(表格)详解 安装: pip install xlrd 官网地址: https://xlrd.readthedocs.io/ 介绍: 为开发人员提供一个库,用于从M ...
- docker搭建rabbitmq镜像集群
Rabbitmq普通集群模式,是将交换机.绑定.队列的元数据复制到集群里的任何一个节点,但队列内容只存在于特定的节点中,客户端通过连接集群中任意一个节点,即可以生产和消费集群中的任何队列内容(因为每个 ...
- C/C++ 以及 Rust 中的 getch() 实现
getch 是一个在 C 语言编程中常用的函数,用于从键盘读取一个字符,但不回显到屏幕上. 在 Windows 环境下,getch 实现通常包含在 <conio.h> 头文件中.需要注意的 ...
- 关于galaxy戒色的通知
明天开始--一小段时间内辅以半退网 如果想打胶 就做100个卷腹 睡不着就吃褪黑素 恁还是多写写诗吧,恁现在这个精虫上脑的脑子连意识流都扛不住 恁还想写<阑山><莲天>< ...
- galaxy特色胡思乱想
有没有什么办法,让我不伤害任何人,什么也不破坏,被判死刑.我觉得这样比我紫砂要好的多. 我所可怜的是神不能紫砂.--芥川龙之介<某傻子的一生>
- SuiteCRM 7.11.18 安装及汉化
SuiteCRM 7.11.18 安装及汉化 sourceforge下载,github也有https://sourceforge.net/projects/suitecrm/files/SuiteCR ...
- 【Java基础总结】集合框架
集合和数组的区别 集合只存储对象,长度是可变的: 数组既可以存储基本数据类型,又可以存储对象,但长度是固定的. 1. Collection接口 代码演示 1 List<String> c1 ...
- NetCore.Encrypt —— 整合加密
前言 最近呢又接触到加密了,回顾之前用到的加密经历,使用过DES.RSA.MD5.BASE64,前面也更新过两篇加密的文章,MD5加密和DES加密.之前的使用都是在.Net Framework平台,这 ...