springboot+mybatis+mybatis-plus对crud项目进行改进

news2024/11/25 6:43:28

springboot+mybatis实现简单的增、删、查、改https://blog.csdn.net/heyl163_/article/details/132197201上一篇文章,已经详细地介绍了怎么通过springboot项目整合mybatis实现简单的数据库表的增删改查功能,是最简单的springboot项目的结构。所以有很多问题,包括简单sql语句需要重复编写、数据校验、异常处理、操作类方法没有返回响应等等。这篇文章我们通过使用springmvc的全局异常处理机制以及引入mybatis-plus和validation来解决这几个问题。

基于上一篇文章的项目,新建一个分支springboot1.0,保存最新的代码。

一、简单sql语句重复编写问题

通过引入mybatis-plus来解决重复编写简单sql语句的问题。

在pom.xml中引入mybatis-plus的依赖

<properties>
    <mybatis-plus.version>3.5.1</mybatis-plus.version>
</properties>

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

然后SongMapper继承BaseMapper接口,然后就能调用BaseMapper里预先定义的crud方法了。

package com.example.springboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springboot.entity.Song;
import org.springframework.stereotype.Repository;

/**
 * @author heyunlin
 * @version 1.0
 */
@Repository
public interface SongMapper extends BaseMapper<Song> {

}

BaseMapper的参数类型为对应实体类的类型;

 

因为BaseMapper里有几个方法selectById()、deleteById()、updateById()需要用到表的主键,因此,在实体类上需要通过注解@TableId来标注哪个字段是表的主键;

 

如果数据库表名和实体类的类名不一致,需要通过@TableName注解指明对应的数据库表;

 

如果数据库表名的字段名和实体类的属性名不一致,需要通过@TableField注解指明对应的数据库表的字段;

package com.example.springboot.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 歌曲
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("song")
public class Song implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(type = IdType.INPUT)
    private String id;

    /**
     * 歌曲名
     */
    @TableField("name")
    private String name;

    /**
     * 歌手
     */
    @TableField("singer")
    private String singer;

    /**
     * 描述信息
     */
    @TableField("note")
    private String note;

    /**
     * 最后一次修改时间
     */
    @TableField("last_update_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime lastUpdateTime;
}

然后,删除SongMapper.java中编写的方法和对应SongMapper.xml中的statement

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.springboot.mapper.SongMapper">
    
</mapper>

删除之后惊奇地发现service中调用mapperde的方法并没有报错,因为这些方法已经预定义在BaseMapper中了。

mybatis-plus使用简单,归根于BaseMapper中预先帮我们实现的方法,接下来详细介绍这些方法的作用。

package com.baomidou.mybatisplus.core.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;

public interface BaseMapper<T> extends Mapper<T> {
    // 添加,如果字段值为null,则不会设置到insert语句中
    int insert(T entity);

    // 通过id删除,id字段的类型需要实现Serializable接口
    int deleteById(Serializable id);

    int deleteById(T entity);

    // 条件删除,把条件放到Map中,map的key是数据库表的字段名,map的value是字段的值
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);

    // 条件删除,通过条件构造器来设置条件
    int delete(@Param("ew") Wrapper<T> queryWrapper);

    // 通过ID批量删除,相当于delete from xxx where id in idList
    int deleteBatchIds(@Param("coll") Collection<?> idList);

    // 通过id修改,如果字段值为null,则不会设置到update语句中
    int updateById(@Param("et") T entity);

    // 条件修改,通过条件构造器来设置条件,entity中的数据为Wrapper的set()方法设置的字段值
    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

    // 通过id查询,id字段的类型需要实现Serializable接口
    T selectById(Serializable id);

    // 通过ID批量查询,相当于select * from xxx where id in idList
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    // 条件查询,通过条件构造器来设置查询条件
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);

    // 条件查询,通过条件构造器来设置查询条件
    // 如果确定查询最多只有一行结果,可以使用该方法,否则将报错
    default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {
        List<T> ts = this.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(ts)) {
            if (ts.size() != 1) {
                throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records", new Object[0]);
            } else {
                return ts.get(0);
            }
        } else {
            return null;
        }
    }

    // existes语句
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0L;
    }

    // 条件查询记录数,通过条件构造器来设置查询条件
    Long selectCount(@Param("ew") Wrapper<T> queryWrapper);

    // 条件查询,通过条件构造器来设置查询条件
    // 当查询结果有多条时,不能使用selectOne,需要使用该方法
    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);

    // 分页查询
    <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);

    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
}

 

二、数据校验问题

1、ID不能为空的问题

delete()和detail()方法理论上需要ID不为空,否则查询结果必然是空,为了避免无效的查询,应该设置id不能为空,通过@RequestParam(requied = true)来限制id不能为空

SongController.java对应修改

@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public void delete(@RequestParam(required = true) @PathVariable("id") String id) {
    songService.delete(id);
}
@RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
public Song detail(@RequestParam(required = true) @PathVariable("id") String id) {
    return songService.detail(id);
}

如下图,当通过postman测试接口时,不传id会报错

cc8c86b7b65f4c908e0bb4616d3c8f0e.png

传正确的ID时,成功返回了查询结果。

0e911367c75249babdf03f6fe2761d03.png

 

2、添加校验

通过validation数据校验框架完成数据校验。

在pom.xml中引入validation的依赖

<!--validation-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

然后创建一个对应前端传来的数据的dto对象,比如添加对应一个dto对象,用户只需要输入name、singer和note三个字段的值,而且name、singer这两个字段是必填的,也就是需要验证非空(ID是通过当前时间生成的)。

10579a2651764c7eb8376c8f57e3b840.png

 添加时的dto对象

package com.example.springboot.dto;

import lombok.Data;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongInsertDTO {
    /**
     * 歌曲名
     */
    private String name;

    /**
     * 歌手
     */
    private String singer;

    /**
     * 描述信息
     */
    private String note;
}

接着,在字段上使用validation定义的注解对字段值进行校验,校验失败会抛出BindException异常,然后对异常进行处理即可,详情请参考文章:

validation数据校验框架https://blog.csdn.net/heyl163_/article/details/132112153

SongInsertDTO.java

package com.example.springboot.dto;

import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongInsertDTO {
    /**
     * 歌曲名
     */
    @NotNull(message = "歌曲名不能为空")
    @NotEmpty(message = "歌曲名不能为空")
    private String name;

    /**
     * 歌手
     */
    @NotNull(message = "歌手不能为空")
    @NotEmpty(message = "歌手不能为空")
    private String singer;
}

然后控制器SongController上的insert()方法的参数类型改为SongInsertDTO,然后在参数上添加@Valid或@Validated注解

@RequestMapping(value = "/insert", method = RequestMthod.POST)
public void insert(@Validated SongInsertDTO insertDTO) {
        songService.insert(insertDTO);
}

相应的,service也要修改

SongService

void insert(SongInsertDTO insertDTO);

SongServiceImpl 

@Override
public void insert(SongInsertDTO insertDTO) {
    Song song = new Song();

    song.setId(StringUtils.uuid());
    song.setName(insertDTO.getName());
    song.setSinger(insertDTO.getSinger());
    
    if (StringUtils.isNotEmpty(insertDTO.getNote())) {
        song.setNote(insertDTO.getNote());
    }

    songMapper.insert(song);
}

 

3、修改校验

修改时需要ID、name、singer不为空,则创建一个SongUpdateDTO类即可,然后按照相同的步骤在类的属性上和控制器方法的参数上添加对应的注解即可。

package com.example.springboot.dto;

import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongUpdateDTO {

    /**
     * 歌曲编号/ID
     */
    @NotNull(message = "歌曲编号不能为空")
    @NotEmpty(message = "歌曲编号不能为空")
    private String id;
    
    /**
     * 歌曲名
     */
    @NotNull(message = "歌曲名不能为空")
    @NotEmpty(message = "歌曲名不能为空")
    private String name;

    /**
     * 歌手
     */
    @NotNull(message = "歌手不能为空")
    @NotEmpty(message = "歌手不能为空")
    private String singer;

    /**
     * 描述信息
     */
    private String note;
}

控制器SongController上的update()方法的参数类型改为SongUpdateDTO,然后在参数上添加@Valid或@Validated注解 

@RequestMapping(value = "/update", method = RequestMethod.POST)
public void update(@Valid SongUpdateDTO updateDTO) {
    songService.update(updateDTO);
}

相应的,service也要修改 

SongService

void update(SongUpdateDTO updateDTO);

SongServiceImpl 

@Override
public void update(SongUpdateDTO updateDTO) {
    Song song = new Song();

    song.setId(StringUtils.uuid());
    song.setName(updateDTO.getName());
    song.setSinger(updateDTO.getSinger());

    if (StringUtils.isNotEmpty(updateDTO.getNote())) {
        song.setNote(updateDTO.getNote());
    }
    song.setLastUpdateTime(LocalDateTime.now());

    songMapper.updateById(song);
}

 

三、操作类方法没有响应数据问题

无论操作成功还是失败,都应该向客户端返回操作的结果。

1、创建响应对象

新建一个响应对象实体类,包含状态码、数据和响应的消息(提示)。

package com.example.springboot.restful;

import lombok.Data;

@Data
public class JsonResult<T> {

    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应提示信息
     */
    private String message;
    /**
     * 响应数据
     */
    private T data;

    public static JsonResult<Void> success() {
        return success(null);
    }

    public static JsonResult<Void> success(String message) {
        return success(message, null);
    }

    public static <T> JsonResult<T> success(String message, T data) {
        JsonResult<T> jsonResult = new JsonResult<>();

        jsonResult.setCode(ResponseCode.OK.getValue());
        jsonResult.setMessage(message);
        jsonResult.setData(data);

        return jsonResult;
    }

    public static JsonResult<Void> error(String message) {
        JsonResult<Void> jsonResult = new JsonResult<>();

        jsonResult.setCode(ResponseCode.ERROR.getValue());
        jsonResult.setMessage(message);

        return jsonResult;
    }

    public static JsonResult<Void> error(ResponseCode responseCode, Throwable e) {
        return error(responseCode, e.getMessage() != null ? e.getMessage() : "系统发生异常,请联系管理员!");
    }

    public static JsonResult<Void> error(ResponseCode responseCode, String message) {
        JsonResult<Void> jsonResult = new JsonResult<>();

        jsonResult.setCode(responseCode.getValue());
        jsonResult.setMessage(message);

        return jsonResult;
    }

}

 

2、定义响应状态码

package com.example.springboot.restful;

/**
 * 响应状态码
 * @author heyunlin
 * @version 1.0
 */
public enum ResponseCode {
    
    /**
     * 请求成功
     */
    OK(200),
    /**
     * 失败的请求
     */
    BAD_REQUEST(400),
    /**
     * 未授权
     */
    UNAUTHORIZED(401),
    /**
     * 禁止访问
     */
    FORBIDDEN(403),
    /**
     * 找不到
     */
    NOT_FOUND(404),
    /**
     * 不可访问
     */
    NOT_ACCEPTABLE(406),
    /**
     * 冲突
     */
    CONFLICT(409),
    /**
     * 服务器发生异常
     */
    ERROR(500);

    private final Integer value;

    ResponseCode(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }

}

 

3、增删改方法添加返回值

insert()、update()、delete()三个方法的返回值类型修改为JsonResult<Void>,JsonResult<T>的参数类型T表示返回的数据的类型,在这里不需要返回数据,只需要返回给用户看的提示信息。

SongController.java

package com.example.springboot.controller;

import com.example.springboot.dto.SongInsertDTO;
import com.example.springboot.dto.SongUpdateDTO;
import com.example.springboot.entity.Song;
import com.example.springboot.restful.JsonResult;
import com.example.springboot.service.SongService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * @author heyunlin
 * @version 1.0
 */
@RestController
@RequestMapping(path = "/song", produces="application/json;charset=utf-8")
public class SongController {

    private final SongService songService;

    @Autowired
    public SongController(SongService songService) {
        this.songService = songService;
    }

    @RequestMapping(value = "/insert", method = RequestMethod.POST)
    public JsonResult<Void> insert(@Validated SongInsertDTO insertDTO) {
        songService.insert(insertDTO);

        return JsonResult.success("添加成功");
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
    public JsonResult<Void> delete(@RequestParam(required = true) @PathVariable("id") String id) {
        songService.delete(id);

        return JsonResult.success("删除成功");
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public JsonResult<Void> update(@Valid SongUpdateDTO updateDTO) {
        songService.update(updateDTO);

        return JsonResult.success("修改成功");
    }

    @RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
    public Song detail(@RequestParam(required = true) @PathVariable("id") String id) {
        return songService.detail(id);
    }

}

这样的话,用户操作添加、删除、修改歌曲时,能得到响应,成功或者失败,以及失败的原因。

 

4、深入分析操作影响行数 

当然了,这里只要没报错就默认操作成功了,如果要求完美,可以获取BaseMapper的增删改方法的返回值,这个返回值是int类型,表示本次操作受影响的行数:插入行数、删除行数、修改行数,根据这个行数返回对应的提示给用户即可。

对应SongServiceImpl的修改

package com.example.springboot.service.impl;

import com.example.springboot.dto.SongInsertDTO;
import com.example.springboot.dto.SongUpdateDTO;
import com.example.springboot.entity.Song;
import com.example.springboot.exception.GlobalException;
import com.example.springboot.mapper.SongMapper;
import com.example.springboot.restful.ResponseCode;
import com.example.springboot.service.SongService;
import com.example.springboot.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * @author heyunlin
 * @version 1.0
 */
@Service
public class SongServiceImpl implements SongService {

    private final SongMapper songMapper;

    @Autowired
    public SongServiceImpl(SongMapper songMapper) {
        this.songMapper = songMapper;
    }

    @Override
    public void insert(SongInsertDTO insertDTO) {
        Song song = new Song();

        song.setId(StringUtils.uuid());
        song.setName(insertDTO.getName());
        song.setSinger(insertDTO.getSinger());

        if (StringUtils.isNotEmpty(insertDTO.getNote())) {
            song.setNote(insertDTO.getNote());
        }

        int rows = songMapper.insert(song);
        
        if (rows < 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "添加时发生了异常,预计影响" + rows + "条记录,已经对本次操作影响的数据进行恢复");
        }
    }

    @Override
    public void delete(String id) {
        int rows = songMapper.deleteById(id);

        if (rows == 0) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "删除失败,本次操作删除了" + rows + "条记录。");
        } else if (rows > 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "删除失败,操作过程中发生了不可预知的错误。");
        }
    }

    @Override
    public void update(SongUpdateDTO updateDTO) {
        Song song = new Song();

        song.setId(StringUtils.uuid());
        song.setName(updateDTO.getName());
        song.setSinger(updateDTO.getSinger());

        if (StringUtils.isNotEmpty(updateDTO.getNote())) {
            song.setNote(updateDTO.getNote());
        }
        song.setLastUpdateTime(LocalDateTime.now());

        int rows = songMapper.updateById(song);

        if (rows == 0) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "修改失败,本次操作删除了" + rows + "条记录。");
        } else if (rows > 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "修改失败,操作过程中发生了不可预知的错误。");
        }
    }

    @Override
    public Song detail(String id) {
        return songMapper.selectById(id);
    }

}

 

四、异常处理问题

程序的健壮性作为3大重要指标,可见非常重要,当程序发生异常时,应该被很好的处理,而不是让用户看到500的页面,或者长时间无响应。

这篇文章通过springmvc统一异常处理机制来解决这个问题,不需要在每个方法上使用try...catch...finally来手动处理异常。

首先,创建一个自定义异常,继承RuntimeException,这时候可能你就会好奇:为什么是继承RuntimeException呢?能不能继承其他Exception,答案是可以。

但是这里需要了解检查异常和非检查异常的区别:

非检查异常:RuntimeException及其子类异常,不需要手动处理,也就是不需要通过try...catch捕获或者通过throws关键字在方法上面申明抛出异常。

检查异常:除了RuntimeException及其子类型异常以外的异常都是检查异常,必须手动处理,通过try...catch捕获或者通过throws关键字在方法上面申明抛出异常。如果不处理,编译就会报错。

所以,我们使用非检查异常的好处显而易见,当我们在代码里主动使用throw关键字抛出RuntimeException异常时不需要去处理就可以通过编译。

我们创建一个exception子包来存放异常相关的类,在该类下创建一个全局异常类GlobalException

package com.example.springboot.exception;

import com.example.springboot.restful.ResponseCode;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 自定义异常
 * @author heyunlin
 * @version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class GlobalException extends RuntimeException {
    private ResponseCode responseCode;

    public GlobalException(ResponseCode responseCode, String message) {
        super(message);

        setResponseCode(responseCode);
    }

}

然后在当前的exception包下面创建一个handler,然后在handler包下创建一个统一异常处理类

package com.example.springboot.exception.handler;

import com.example.springboot.exception.GlobalException;
import com.example.springboot.restful.JsonResult;
import com.example.springboot.restful.ResponseCode;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * 全局异常处理类
 * @author heyunlin
 * @version 1.0
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理GlobalException
     * @param e GlobalException
     * @return JsonResult<Void>
     */
    @ExceptionHandler(GlobalException.class)
    public JsonResult<Void> handlerGlobalException(HttpServletResponse response, GlobalException e) {
        e.printStackTrace();
        response.setStatus(e.getResponseCode().getValue());

        return JsonResult.error(e.getResponseCode(), e);
    }

    /**
     * 处理BindException
     * @param e BindException
     * @return JsonResult<Void>
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handlerBindException(BindException e) {
        e.printStackTrace();

        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

        return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage);
    }

    /**
     * 处理Exception
     * @param e Exception
     * @return JsonResult<Void>
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult<Void> handlerException(Exception e) {
        e.printStackTrace();

        return JsonResult.error(ResponseCode.ERROR, e);
    }

}

这样的话,当发生对应的异常时,会执行对应的方法,比如数据校验失败时发生了BindException,会执行以下方法

/**
 * 处理BindException
 * @param e BindException
 * @return JsonResult<Void>
 */
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public JsonResult<Void> handlerBindException(BindException e) {
        e.printStackTrace();

 

        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

 

        return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage);
}

通过上面几个步骤,项目的结构已经相对完善了。

 

好了,这篇文章就分享到这里了,看完不要忘了点赞+收藏哦~

源码已经上传至git,按需获取:

springboot整合mybatis实现crud项目改进https://gitee.com/he-yunlin/springboot/tree/springboot1.0/

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/865590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FinClip | 7月做出了一些微不足道的贡献

FinClip 的使命是使您&#xff08;业务专家和开发人员&#xff09;能够通过小程序解决关键业务流程挑战&#xff0c;并完成数字化转型的相关操作。不妨让我们看看在本月的产品与市场发布亮点&#xff0c;看看是否有助于您实现目标。 产品方面的相关动向&#x1f447;&#x1f…

Python中的排序

一、列表排序 举例sort和sorted对列表排序&#xff0c;说明两者的区别。 import relist1 [0, -1, 3, -10, 5, 9] list1.sort(reverseFalse) print(list1.sort在list1基础上修改&#xff0c;无返回值, list1) list2 [0, -1, 3, -10, 5, 9] res sorted(list2, reverseFalse)…

团队管理之PDP大法

PDP 是什么&#xff0c;为什么有些人会谈PDP色变呢&#xff1f;人常常会对自己不了解的东西感到恐惧 一、什么是PDP 团队管理中的PDP可能指"Personal Development Plan"&#xff08;个人发展计划&#xff09;&#xff0c;它是一种用于帮助团队成员提升个人能力和达成…

leetcode 面试题 02.07. 链表相交

题目&#xff1a;leetcode 面试题 02.07. 链表相交 描述&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 思路&…

ARP请求拦截及响应

一、前言 本文主要是介绍如何对arp请求包进行拦截&#xff0c;并代替系统进行响应。对arp请求进行拦截&#xff0c;需要在驱动中进行&#xff0c;具体代码如下文所示。&#xff08;本文仅供参考&#xff09; 二、环境 OS Ubuntu 20.04.6 LTSLinux ubuntu 5.15.0-71-generic三…

Java培训班出来能找到工作吗?有没有想详细了解的呢

参加Java培训班可以提升你的编程技能和就业竞争力&#xff0c;但能否找到工作还取决于多个因素&#xff0c;如个人能力、市场需求、就业竞争等。参加Java培训班可以帮助你获得系统的Java编程知识和实践经验&#xff0c;了解行业最佳实践和流行的技术框架。这有助于你在面试时展…

SpringBoot案例-部门管理-删除

目录 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文档 思路分析 功能接口开发 控制层&#xff08;Controllre类&#xff09; 业务层&#xff08;Service类&#xff09; 持久层&#xff08;Mapper类&#xff09; 接口测试 前后端联调 查看页面原型&a…

Linux进程管理命令

一、进程 程序由一条条指令构成&#xff0c;在运行一个程序的时候就是把这些指令从第一条执行到最后一条&#xff0c;而进程是一个正在运行的程序。 比如说&#xff0c;一个main.c文件是不可以直接运行的&#xff0c;对main.c进行编译链接之后生成一个main.exe&#xff08;在W…

QT学习笔记-QT安装oracle oci驱动

QT学习笔记-QT安装oracle oci驱动 0、背景1、环境以及条件说明2、编译驱动2.1 下载oracle instant client2.2 编译qt oci驱动2.2.1 修改oci.pro2.2.2 MinGW64构建套件编译2.2.3 MSVC2019_64构建套件编译 3、访问数据库运行成功 0、背景 在使用QT开发应用的过程中&#xff0c;往…

Mysql SUBSTRING_INDEX - 按分隔符截取字符串

作用&#xff1a; 按分隔符截取字符串 语法&#xff1a; SUBSTRING_INDEX(str, delimiter, count) 属性&#xff1a; 参数说明str必需的。一个字符串。delimiter必需的。分隔符定义&#xff0c;是大小写敏感&#xff0c;且是多字节安全的count必须的。大于0或者小于0的数值…

案例分析丨大数据平台和应用测试,应该关注哪些点?

互联网的发展催生了大数据行业的诞生和发展。大数据平台和大数据应用成为了各家排兵布阵的重要之地。那么&#xff0c;从测试的视角来看&#xff0c;大数据平台和应用的测试&#xff0c;我们应该关注哪些点呢&#xff1f; 换个姿势看问题。今天我们从问题域的角度来聊一聊。 什…

【数据处理-番外篇】手写了几个数据处理,都是用的递归

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; JavaScript | 脚本语言 开发工具&#xff1a;Vs Code 数据处理 对象修改结构判断两对象是否全等(只针对对象未做其他类型)复杂结构去重我写的破代码(没用,逻辑,结构都不对) 一些原理我也不讲了&#xff0c;我就是记录一…

MySQL不走索引的情况分析

未建立索引 当数据表没有设计相关索引时&#xff0c;查询会扫描全表。 create table test_temp (test_id int auto_incrementprimary key,field_1 varchar(20) null,field_2 varchar(20) null,field_3 bigint null,create_date date null );expl…

【C++】虚继承(virtual base classes)

【C】虚继承&#xff08;virtual base classes) 文章目录 【C】虚继承&#xff08;virtual base classes)1. 使用原因2. 解决方法3. 例题练习 1. 使用原因 在多重继承(Multiple Inheritance) 的情况下&#xff0c;尤其是菱形继承时&#xff0c;容易出现问题&#xff0c;关于菱…

STM32F429IGT6使用CubeMX配置GPIO点亮LED灯

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置GPIO引脚 4、生成工程配置 5、部分代码 6、实验现象

CentOS7有线未托管,网络连接图标消失

问题描述 网络图标消失&#xff0c;显示“有线 未托管”&#xff0c;且无法连接网络 解决方案 ①编辑文件&#xff1a;vim /etc/sysconfig/network-scripts/ifcfg-ens33 ②删除NM_CONTROLLEDno ③重启网络&#xff1a;service network restart 立马就可以自动连接上网络&…

SqlServer基础之(触发器)

概念&#xff1a; 触发器&#xff08;trigger&#xff09;是SQL server 提供给程序员和数据分析员来保证数据完整性的一种方法&#xff0c;它是与表事件相关的特殊的存储过程&#xff0c;它的执行不是由程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#x…

面试热题(两数之和)

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答…

并发——JDK 提供的并发容器总结

文章目录 一 JDK 提供的并发容器总结二 ConcurrentHashMap三 CopyOnWriteArrayList3.1 CopyOnWriteArrayList 简介3.2 CopyOnWriteArrayList 是如何做到的&#xff1f;3.3 CopyOnWriteArrayList 读取和写入源码简单分析3.3.1 CopyOnWriteArrayList 读取操作的实现3.3.2 CopyOnW…

K8S MetalLB LoadBalancer

1. 简介 kubernetes集群没有L4负载均衡&#xff0c;对外暴漏服务时&#xff0c;只能使用nodePort的方式&#xff0c;比较麻烦&#xff0c;必须要记住不同的端口号。 LoadBalancer&#xff1a;使用云提供商的负载均衡器向外部暴露服务&#xff0c;外部负载均衡器可以将流量路由…