速通java,微服务开始(第一天MybatisPlus)(P1~P20)

快速入门

配置想要什么去官网添加哪个

rewriteBatchedStatements=true批处理,配置在关于数据库的url中(与Mybatis无关)

第一步Mybatis依赖引入

<dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
</dependency>

第二步,在Mapper接口上认爸爸,也就是继承

public interface UserMapper extends BaseMapper<User> {


}

继承BaseMapper<T> ,泛型要指定对应的实体类

基础的增删改查对应insert,delete,update,select。

那么MabstisPlus怎么知道我们需要改动哪张表呢?
 

泛型中的User就是与数据库对应的PO.

MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名

  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型

  • MybatisPlus会把名为id的字段作为主键

但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。

常见注解

MybatisPlus小规则

MybatisPlus通过扫描对应的实体类经过反射获取信息

规则一:实体类类名驼峰转下划线为表名

规则二:变量名字驼峰转下划线为字段名字

规则三:自动把为id的变量视为主键

常用注解

@TableName:用来指定表名

@TableId:用来指定表中的主键字段信息

有一个type属性,默认的是(type = IdType.NONE)是枚举类型

这里比较常见的有三种:

  • AUTO:利用数据库的id自增长

  • INPUT:手动生成id

  • ASSIGN_ID:雪花算法生成Long类型的全局唯一id,这是默认的ID策略

@TableField:用来指定表中的普通字段信息

没有指定id类型默认雪花算法生成

常见配置

大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如:

  • 实体类的别名扫描包

  • 全局id类型

  • mybatis-plus:
     type-aliases-package:  com.itheima.mp.domain.po
     global-config: 
       db-config:
         id-type: auto # 全局id类型为自增长

需要注意的是,MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置:

mybatis-plus:
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。

可以看到默认值是classpath*:/mapper/**/*.xml,也就是说我们只要把mapper.xml文件放置这个目录下就一定会被加载。

条件构造器(复杂的业务)

其中参数Wrapper就是负责构造条件的抽象类

基于QueryWrapper

查询名字中带o的用户,且存款大于1000

@Test
    void testQueryWrapper(){
        //1.构建查询条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id","username","info","balance")
                .like("username","o")
                .ge("balance",1000);
        //2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

更新名字为jack的人余额为2000

@Test
    void testUpdateByQueryWrapper(){
        //1.要更新的数据
        User user = new User();
        user.setBalance(2000);
        //2.更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username","jack");
        //3.执行更新
        userMapper.update(user,wrapper);
    }

基于UpdateWrapper

更新id为1,2,4的用户,资金扣两百。

/**
     * 更新id为1,2,4的用户,资金扣两百
     */
    @Test
    void testUpdateWrapper() {
        List<Long> ids = List.of(1L, 2L, 4L);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id",ids);
        userMapper.update(null,wrapper);
    }

基于LambdaQueryWrapper

因为上面我们在代码中写死了字段名,所以我们寻求一个既不写死但是还能写入字段名的方法

其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:

  • LambdaQueryWrapper

  • LambdaUpdateWrapper

分别对应QueryWrapper和UpdateWrapper

@Test
void testLambdaQueryWrapper() {
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000);
    // 2.查询
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
@Test
    void testLambdaQueryWrapper(){
        //1.构建查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId,User::getUsername,User::getInfo,User::getBalance)
                .like(User::getUsername,"o")
                .ge(User::getBalance,1000);
        //2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

其实就是把死的字段替换为了get方法获取

自定义SQL

因为上面写balence = balence - 200是直接写在了服务层,但是要是都写在xml文件里面有in,还需要foreach ,所以有了自定义SQL。

我们使用mp构建条件,但是剩下的我们自己写

测试类

@Test
    void testCustomSqlUpdate(){
        //1.更新条件
        List<Long> ids = List.of(1L,2L,3L);
        int amout = 200;
        //2.定义条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id",ids);
        //3.调用自定义方法
        userMapper.updateBalanceByIds(wrapper,amout);
    }

UserMapper

 void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper,@Param("amout") int amout);

对于传进来的wrapper对象必须要使用@Param("ew"),且只有这一个注解指定名称

UserMapper.xml

    <update id="updateBalanceByIds">
            update user set balance = balance - #{amout} ${ew.customSqlSegment}
    </update>

多表关联使用

假设我们查询为出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
      SELECT *
      FROM user u
      INNER JOIN address a ON u.id = a.user_id
      WHERE u.id
      <foreach collection="ids" separator="," item="id" open="IN (" close=")">
          #{id}
      </foreach>
      AND a.city = #{city}
  </select>

但是我们使用自定义SQL,我们用MybatisPlus只写限制条件,代码如下

@Test
void testCustomJoinWrapper() {
    // 1.准备自定义查询条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .in("u.id", List.of(1L, 2L, 4L))
            .eq("a.city", "北京");

    // 2.调用mapper的自定义方法
    List<User> users = userMapper.queryUserByWrapper(wrapper);

    users.forEach(System.out::println);
}

Mapper层代码如下

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

Service接口

CRUD

通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增

  • remove:删除

  • update:更新

  • get:查询单个结果

  • list:查询集合结果

  • count:计数

  • page:分页查询

IUserService

public interface IUserService extends IService<User> {
}

UserService

泛型传入的两个类型第一个是对应的Mapper接口,第二个是实体类User

ServiceImpl封装的是IService的实现方法。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements IUserService{
}

例子

@Test
    void testSaveUser(){
        User user = new User();
        //user.setId(5L);
        user.setUsername("LiLei");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        iUserService.save(user);
    }

    @Test
    void testQuery(){
        List<User> users = iUserService.listByIds(List.of(1L,2L,4L));
        users.forEach(System.out::println);
    }

接口例子

    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFormDTO userDTO){
        //1.把DTO拷贝到PO里面
        User user = BeanUtil.copyProperties(userDTO,User.class);
        //2.新增
        userService.save(user);
    }

    @ApiOperation("删除用户接口")
    @DeleteMapping("{id}")
    public void deleteUserById( @ApiParam("用户id") @PathVariable("id") Long id){
        userService.removeById(id);
    }

    @ApiOperation("根据id查询用户接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
        //1.查询返回的是PO
        User user = userService.getById(id);
        //2. 把PO拷贝到VO返回
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @ApiOperation("批量根据id查询用户接口")
    @GetMapping
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
        //1.查询返回的是PO
        List<User> users = userService.listByIds(ids);
        //2. 把PO拷贝到VO返回
        return BeanUtil.copyToList(users, UserVO.class);
    }

    @ApiOperation("根据复杂条件查询用户")
    @GetMapping("/list")
    public List<UserVO> queryUsers(UserQuery query){
        //1.查询用户PO
        List<User> users = userService.queryUsers(query.getName(),query.getStatus(),query.getMinBalance(),query.getMaxBalance());
        //2. 把PO拷贝到VO返回,用这个方法直接返回的是一个关于一个UserVO集合
        return BeanUtil.copyToList(users, UserVO.class);
    }

以上接口直接调用

糊涂包的属性拷贝是有两种类型,第一种是创建好对象拷贝进去,只是起到赋值的作用

第二种就是,BeanUtil.copyToList(users, UserVo.class)直接生成对象

复杂案例

controller
@ApiOperation("扣减用户余接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(@ApiParam("用户id") @PathVariable("id") Long id,
                              @ApiParam("扣减的金额") @PathVariable("money") Integer money){
        userService.deductBalance(id,money);
    }
IService
void deductBalance(Long id, Integer money);
UserServiceImpl
 @Override
    @Transactional
    public void deductBalance(Long id, Integer money) {
        //1.查询用户
        User user = getById(id);//因为自己就是服务层本身,可以直接使用方法
        //2.校验用户状态
        if(user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //3.校验余额是否充足
        if(user.getBalance() < money){
            throw new RuntimeException("用户余额不足");
        }
        //4.扣减余额update user set balance = balance - ?
        //第一种写法
        //baseMapper.deductBalance(id,money);//baseMapper其实就是UserMapper,因为在开头继承的UserServiceImpl不仅实现了接口方法,也注入了UserMapper,之所以叫baseMapper是因为指定的是泛型
        //第二种写法,若余额为0则修改状态
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance,remainBalance)
                .set(remainBalance == 0,User::getStatus,2)
                .eq(User::getId,id)
                .eq(User::getBalance,user.getBalance())//乐观锁,假如有多条线程同时访问这一个查询,就可能同时进入就扣了一百元,所以需要判断前后金额是否相同
                .update();
    }

复杂更新建议用LambdaUpdate(),先set再添加条件,第二个set就是假如余额为0的话就更新状态。

扩展功能

代码生成

在tool里面先连接数据库config datebase

再Code Generator

静态工具(DB)(Service多表调用)

Controller(跟据id单个查询)
@ApiOperation("根据id查询用户以及地址接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){

        return userService.queryUserAndAddressById(id);
    }
UserServiceImpl
@Override
    public UserVO queryUserAndAddressById(Long id) {
        //1.查询用户
        User user = getById(id);
        if(user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId,id).list();
        //3.封装
        //3.1转User的po为vo
        UserVO userVO = BeanUtil.copyProperties(user,UserVO.class);
        //3.2转地址VO
        if(CollUtil.isNotEmpty(addresses)){
            userVO.setAddress(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        return userVO;
    }

使用了Db调用LambdaQuery(Sddress.class)不用再注入AddressService调用方法,直接用字节码class

使用在Service相互调用的时候

Conllertroller(跟据id列表批量查询)
@ApiOperation("批量根据id查询用户及地址接口")
    @GetMapping
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){

        return userService.queryUserAndAddressByIds(ids);
    }
UserServiceImpl
@Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        //1.查询用户
        List<User> userList = listByIds(ids);
        if(CollUtil.isEmpty(userList)){
            return Collections.emptyList();
        }
        //2.查询住址
        //2.1跟据用户id查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId,ids).list();
        //2.2转换地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        //2.3用户地址分批处理,相同用户的放入一个集合中
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) {
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }
        //3.转换VO返回
        List<UserVO> list = new ArrayList<>(userList.size());
        for (User user : userList) {
            //转换user的po为vo
            UserVO vo = BeanUtil.copyProperties(user,UserVO.class);
            list.add(vo);
            //转换地址vo
            vo.setAddress(addressMap.get(user.getId()));
        }
        return list;
    }

请注意,在分组的时候调用了addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId))

逻辑删除

是删除数据时,将其delete字段的值改为1,即为删除,且查询的时候,不仅条件上id相等,而且删除字段需要为0

只需要在yaml文件中配置mybatis的逻辑删除配置即可,此后调用删除与查正常调用即可

但是这种方法不太实用,所以不过多赘述

枚举处理器

状态多的时候不容易分辨,所以需要枚举类,但是实体类虽然转换了,但是从数据库读出来数据还是数字,那么Mubatis有办法,但是需求不太不符合,于是MybatisPlus出了一个新的枚举处理器

@Getter
public enum UserStatus {
    NORMAL(1,"正常"),
    FROZEN(2,"冻结"),
    ;
    @EnumValue
    private final int value;
    @JsonValue
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
@ApiModelProperty("使用状态(1正常 2冻结)")
    private UserStatus status;

第一:要给枚举中与数据库对应的value值添加@EnumValue注解

要给实体类和VO类的status全都设置为枚举类型的

JSON处理器

在转换的时候,JSON对象其实就是一个字符串,我们要是想取出JSON中的某个值的话,就要定义一个这样接收数据的实体类,就可以接收,但是需要我们使用JSON转换器

MyBatisPlus需要利用@TableName(autoResultMap = true)

还有@TableField(typeHandler = JacksonTypeHandler.class)来开启转换器

@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
 @Test
    void testInsert() {
        User user = new User();
        user.setId(5L);
        user.setUsername("Lucy");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo(UserInfo.of(24,"英文老师","female"));
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);//原本要写一大段Mappper方法还有配置的xml文件里面的方法,现在直接调用就可以
        //这是Mybatis的插入方法
    }

分页插件

一个案例

公共的分页查询实体类,哪个需要哪个继承它

定义的是分页结果的数据

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值