MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
任何能使用
mybatis
进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>Latest Versionversion>
dependency>
# 配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wyl? useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
# 配置日志,输出更详细日志信息
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
//在对应的mapper上面继承基本的接口BaseMapper
@ResponseBody //代表持久层
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD操作都已经编写完成了
//你不需要像以前的配置一大堆文件了
}
启动类需要加 @MapperScan(“mapper所在的包”),否则无法加载mapper bean
@SpringBootTest
class MybatisPlusApplicationTests {
//继承了BaseMapper,所有的方法都来自父类
//我们也可以编写自己的扩展方法
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//参数是一个Wrapper,条件构造器,这里我们先不用 null
//查询全部用户
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
那么我的sql 谁写的,方法又去哪了,其实都是mybatis plus。
我们所有的sql现在是不可见的,我们希望知道他是怎么执行的,所以我们必须要看日志!
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
// 根据 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);
// 根据 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<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);
// 无条件分页查询
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);
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);
// 插入一条记录
int insert(T entity);
// 根据 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);
// 根据 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);
案例
List<User> userList1 = user.selectList(
new EntityWrapper<User>().eq("name", "王延领")
);
分页
// 分页查询 10 条姓名为‘wyl’的用户记录
List<User> userList = user.selectPage(
new Page<User>(1, 10),
new EntityWrapper<User>().eq("name", "wyl")
).getRecords();
结合
// 分页查询 10 条姓名为‘wyl’、性别为男,且年龄在18至50之间的用户记录
List<User> userList = userMapper.selectPage(
new Page<User>(1, 10),
new EntityWrapper<User>().eq("name", "wyl")
.eq("sex", 0)
.between("age", "18", "50")
);
int alwaysUpdateSomeColumnById(T entity);
int insertBatchSomeColumn(List<T> entityList);
int logicDeleteByIdWithFill(T entity);
十分重要:Wappper
1、测试一,记住查看输出的SQL进行分析
@Test
void contextLoads() {
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age", 12);
userMapper.selectList(wrapper).forEach(System.out::println); //和我们刚刚学习的map对比一下
}
2、测试二,记住查看输出的SQL进行分析
@Test
void test2(){
//查询名字Chanv
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "Chanv");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
3、测试三
@Test
void test3(){
//查询年龄在19到30岁之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 19, 30); //区间
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
4、测试四,记住查看输出的SQL进行分析
//模糊查询
@Test
void test4(){
//查询年龄在19到30岁之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
//左和右
wrapper.notLike("name", "b")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
5、测试五
@Test
void test5(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//id 在子查询中查出来
wrapper.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
6、测试六
@Test
void test6(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//通过id进行排序
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
dao、pojo、service、controller都给我自己去编写完成!
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.3.1.tmpversion>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocityartifactId>
<version>1.7version>
dependency>
package com.wyl.mybatisplus;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class Code {
public static void main(String[] args) {
//需要构建一个 代码自动生成器 对象
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
//配置策略
//1、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("ChanV");
gc.setOpen(false);
gc.setFileOverride(false); //是否覆盖
gc.setServiceName("%sService"); //去Service的I前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis-plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3、包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog");
pc.setParent("com.chanv");
pc.setEntity("pojo");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user"); //设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); //自动lombok
strategy.setLogicDeleteFieldName("deleted");
//自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
//乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute(); //执行代码构造器
}
}
以上两步即可完成生成代码功能!
启动类上扫描
@SpringBootApplication // 启动类
@MapperScan(value = {"com.wyl.mybatisplus.generator.mapper"}) // 扫描mapper
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
测试类上扫描
@SpringBootTest
@MapperScan(value = {"com.wyl.mybatisplus.generator.mapper"}) // @MapperScan("mapper的包位置")
class UserServiceTest {
@Autowired
private UserMapper mapper;
@Test
public void test(){
mapper.selectList(null).forEach(System.out::println);
}
}
package com.wyl.mybatisplus.generator.controller;
import com.wyl.mybatisplus.generator.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/generator/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/success")
public ModelAndView index(){
ModelAndView mav = new ModelAndView();
mav.setViewName("success");
mav.addObject("list",userService.list());
return mav;
}
}
记得放在templates下哦
index.html
<h1>Index Page...h1>
<a href="/generator/user/success">展示数据a>
succuss.html
DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Success Pagetitle>
head>
<body>
<h1>Success Page...h1>
<table border="1" cellspacing="0" cellpadding="1">
<tr>
<th>编号th>
<th>用户名th>
<th>年龄th>
tr>
<tr th:each="user:${list}">
<td th:text="${user.id}">td>
<td th:text="${user.userName}">td>
<td th:text="${user.userAge}">td>
tr>
table>
body>
html>
前端用到thymeleaf模板引擎,需要配置application.yml
spring:
# 视图解析
thymeleaf:
prefix: classpath:/templates/
suffix: .html
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
在面试过程中,我们经常会被问到乐观锁,悲观锁!这个其实非常简单!
原子引用!
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试!
悲观锁:顾名思义十分悲观,他总是任务总是出现问题,无论干什么都会上锁!再去操作!
我们这里主要讲解,乐观锁机制!
乐观锁实现方式:
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "wyl", version = version + 1
where id = 2 and version = 1
-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "wjm", version = version + 1
where id = 2 and version = 1
测试一下MP的乐观锁插件
1、给数据库中增加version字段!
//测试乐观锁成功!
@Test
public void testOptimisticLocker(){
//1、查询用户信息
User user = userMapper.selectById(1330080433207046145L);
//2、修改用户信息
user.setName("ChanV");
user.setEmail("[email protected]");
//3、执行更新操作
userMapper.updateById(user);
}
//测试乐观锁失败!多线程下
@Test
public void testOptimisticLocker2(){
//线程1
User user = userMapper.selectById(5L);
user.setName("ChanV111");
user.setEmail("[email protected]");
//模拟另一个线程执行了插队操作
User user2 = userMapper.selectById(5L);
user2.setName("ChanV222");
user2.setEmail("[email protected]");
userMapper.updateById(user2);
//自旋锁多次尝试提交
userMapper.updateById(user); //如果没有乐观锁就会覆盖队线程的值
}
通过上面的小案例我们可以发现,实体类需要加@TableName注解指定数据库表名,通过@TableId注解指定id的增长策略。实体类少倒也无所谓,实体类一多的话也麻烦。所以可以在spring-dao.xml的文件中进行全局策略配置。
<bean id="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<property name="idType" value="0"/>
<property name="tablePrefix" value="tb_"/>
bean>
这里配置了还没用,还需要在sqlSessionFactory中注入配置才会生效。如下:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="com.zhu.mybatisplus.entity"/>
<property name="globalConfig" ref="globalConfiguration"/>
bean>
如此一来,实体类中的@TableName注解和@TableId注解就可以去掉了。