工作笔记

数据库

  • 【强制】主键索引名为pk_字段名;

    ​ 唯一索引名为uk_字段名;

    ​ 普通索引名则为idx_字段名。

    ​ 说明:pk_ 即primary key;uk_ 即unique key;idx_ 即index的简称。

  • 【强制】表必备三字段:id, gmt_create, gmt_modified。

    说明:其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmt_create, gmt_modified的类型均为datetime类型,前者现在时表示主动创建,后者过去分词表示被动更新。gmt表示格林威治时间即国际时间。

    1
    2
    3
    4
    5
    6
    # DEFAULT CURRENT_TIMESTAMP  表示当插入数据的时候,该字段默认值为当前时间
    # ON UPDATE CURRENT_TIMESTAM 表示每次更新这条数据的时候,该字段都会更新成当前时间
    CREATE TABLE `test` (
    `id` bigint(20) NOT NULL COMMENT '主键id',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间')
  • 【强制】POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。说明:参见定义POJO类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。在MyBatis Generator生成的代码中,需要进行对应的修改。

  • 自增ID与UUID

    • 自增ID数据存储空间小,性能好,但是安全性低,很难处理分布式存储的数据表,处理大量数据可能会超过自增长的取值范围

    • uuid_short() 生成18位正随机数 ===>bigint(20)

      1
      2
      3
      4
      5
      6
      <insert id="addLocationArea"
      parameterType="">
      INSERT INTO location( ... )
      VALUES
      (SHORT_UUID(), ...)
      </insert>
    • 随机正Long值uuid ===>bigint(20)

    1
    2
    3
    4
    public synchronized static Long getUUID() {
    // 生成19位正随机数
    return UUID.randomUUID().getLeastSignificantBits() * -1;
    }
  • MySQL字段尽量避免NULL,应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,而且对表索引时不会存储NULL值的,所以如果索引的字段可以为NULL,索引的效率会下降很多。因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值。

    1
    2
    3
    4
    # 查询出来的的记录中col列可以为''(空字符串)
    SELECT * FROM `table` WHERE col IS NOT NULL;
    # 查询col列不为''的所有记录
    SELECT * FROM `table` WHERE col1 <> '';

    crud

  • 模糊搜索:

    1
    select id,name from product where name like concat('%',#{name,jdbcType=varchar},'%')
  • 根据不确定的字段条件进行查询:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <resultMap id="ResultMap" type="com.zyz.UserPO">
    <id column="id" property="id" jdbcType="BIGINT"/>
    <result column="name" property="name" jdbcType="VARCHAR" />
    <result column="password" property="password" jdbcType="VARCHAR" />
    <collection property="userItemPOList" ofType="com.zyz.UserItemPO">
    <!--property对应UserPO中的明细对象集合 ofType对应集合对象的实体类-->
    <id column="item_id" property="id" jdbcType="BIGINT"/>
    <result column="school_name" property="name" jdbcType="VARCHAR" />
    <result column="school_address" property="address" jdbcType="VARCHAR" />
    <!--column属性为联表查询中主表以外的表的字段别名-->
    </collection>
    </resultMap>

    <sql id="Base_Column_List" >
    id,name,password
    </sql>

    <select id="list" resultMap="ResultMap">
    select
    <include refid="Base_Column_List" />
    from t_user
    where 1=1
    <if test="PO.id != null" >
    and id = #{PO.id,jdbcType=BIGINT}
    </if>
    <if test="PO.name != null">
    and name = #{PO.name,jdbcType=VARCHAR}
    </if>
    </select>


    <!--批量条件查询 根据商品明细列表中的所有id查询对应的明细详情-->
    <select id="list" resultMap="ResultMap">
    SELECT
    <include refid="chargeofforderitemdetailColumns" />
    FROM chargeofforderitemdetail
    <where>
    <if test="chargeOfforderItemIdList != null and chargeOfforderItemIdList.size() > 0">
    ChargeOfforderItem_Id in
    <foreach collection="chargeOfforderItemIdList" item="item" separator="," open="(" close=")">
    #{item,jdbcType=VARCHAR}
    </foreach>
    </if>
    </where>
    </select>
  • 插入不确定的属性字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <insert id="insert">
    insert into table
    <trim prefix="(" suffix=")" suffixOverrides=",">
    id,
    <if test="PO.name != null">
    name,
    </if>
    create_Time,
    lastUpdate_Time
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
    UUID_SHORT(),
    <if test="PO.name != null">
    #{PO.name,jdbcType=VARCHAR},
    </if>
    now(),
    now()
    </trim>
    </insert>
  • 批量插入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <insert id="insertbatch" parameterType="java.util.List" >
    insert into user(Id,name)values
    <foreach collection="list" item="item" separator="," index="index">
    (
    UUID_SHORT(),
    #{item.name,jdbcType=VARCHAR}
    )
    </foreach>
    </insert>
  • 根据不确定的字段条件进行更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <update id="update">
    update table
    <set>
    <if test="PO.state != null">
    name = #{PO.name,jdbcType=VARCHAR},
    </if>
    <if test="PO.password != null">
    password = #{PO.password,jdbcType=VARCHAR},
    </if>
    </set>
    where id = #{PO.id,jdbc=BIGINT}
    </update>
  • 时间段的处理

    1
    2
    3
    4
    5
    6
    <if test="beginTime != null">
    and <![CDATA[ BusinessTime >= #{beginTime,jdbcType=TIMESTAMP} ]]>
    </if>
    <if test="endTime != null">
    and <![CDATA[ BusinessTime <= #{endTime,jdbcType=TIMESTAMP} ]]>
    </if>

框架

Spring缓存注解

  • 启动类添加@EnableCaching开启缓存

  • Spring Cache是作用在方法上的,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次调用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。

  • @Cacheable(key="" , value="") 方法调用前查询缓存,不存在就会查询数据库,查询完成后放入缓存

  • @CacheEvict(key="" , value="") 调用此注解下的方法时会清除缓存

  • @CachePut(key="" , value="")方法调用完成后放入缓存

    key属性是用来指定Spring缓存方法的返回结果时对应的key,value属性用来指定缓存名称

  • 使用场景:业务层bl中的关于用户的基础增删改查功能。

  • 缺点:因为不能指定数据有效时间,以及读取改变的数据不够灵活。一般使用RedisTemple或者自定义注解的方式。

@Transactional

  • 一般在bl层的方法中执行多条sql时使用
    • @Transactional(rollbackFor = { Exception.class })
    • 默认发生ErrorRuntimeException时才会回滚,发生非运行时异常时不会回滚,需要使用rollbackFor属性指定回滚的非运行时异常。

Controller中的Model参数

  • 只有在前端请求有页面返回的时候用到,保存了想要展示在页面上的数据。
  • 例如数据的导出时,使用到了Model参数。

自定义注解中的元注解

1
2
3
4
// 定义注解使用的时机 METHOD->适用于方法  TYPE->适用于 class,interface,enum
@Target({ElementType.METHOD, ElementType.TYPE})
// 在运行时去动态获取注解信息
@Retention(RetentionPolicy.RUNTIME)

全局异常处理

对类添加@ControllerAdvice注解,在处理异常的方法前添加

1
2
3
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler({Exception.class})
@ResponseBody

CommandLineRunner接口

在应用初始化后,去执行一段代码块逻辑,这段初始化代码在整个应用生命周期内只会执行一次。

用于预先加载数据,实现CommandLineRunner接口,需要加载的数据的逻辑写在run()方法中。

可使用@Order(1)@Order(2)……注解指定加载顺序。

对象的有状态与无状态

  • 是否存储数据

  • 无状态的Bean适合用单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。

Controller中的返回数据

  • ResponseAdvisor对返回数据进行了拦截,然后统一处理,所有的controller只需要返回业务相关数据即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ControllerAdvice // 对controller的全局配置
public class ResponseAdvisor implements ResponseBodyAdvice<Object> {

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {

if (body instanceof Result) {
return body;
}
// 处理数据,将业务数据封装成自定义的结果集对象Result
return RetResponse.makeOKRsp(body);
}
}

RetResponse对controller返回的业务数据封装成自定义的 Result对象返回给前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 响应结果封装 对controller返回的参数封装成自定义的 Result对象返回给前端
*/
public class RetResponse {

public static <T> Result<T> makeOKRsp(T data, String msg) {
return new Result<T>().setCode(ResultCode.SUCCESS) // 200
.setResult(WebConstants.RESULT_SUCCESS)// success
.setData(data).setMsg(msg);// 结果信息
}

public static <T> Result<T> makeOKRsp(T data) {
return new Result<T>()
.setCode(ResultCode.SUCCESS).setResult(WebConstants.RESULT_SUCCESS).setData(data);
}

public static <T> Result<T> makeOKRsp() {
return new Result<T>()
.setCode(ResultCode.SUCCESS).setResult(WebConstants.RESULT_SUCCESS);
}
public static <T> Result<T> makeErrRsp(T data, String msg) {
return new Result<T>() .setCode(ResultCode.INTERNAL_SERVER_ERROR)
.setResult(WebConstants.RESULT_FAILED).setData(data).setMsg(msg);
}

public static <T> Result<T> makeFailRsp(T data, String msg) {
return new Result<T>() .setCode(ResultCode.FAIL)
.setResult(WebConstants.RESULT_FAILED).setData(data).setMsg(msg);
}

public static <T> Result<T> makeRsp(String code) {
return new Result<T>().setCode(Integer.valueOf(code));
}

public static <T> Result<T> makeRsp(String code, String msg) {
return new Result<T>().setCode(Integer.valueOf(code)).setMsg(msg);
}

public static <T> Result<T> makeExceptionRsp(String code, String msg) {
return new Result<T>()
.setCode(Integer.valueOf(code))
.setMsg(msg).setResult(WebConstants.RESULT_FAILED);
}

public static <T> Result<T> makeRsp(Integer code, String msg, T data, String result){
return new Result<T>()
.setCode(code).setMsg(msg).setData(data).setResult(result);
}
}

Result自定义结果集封装对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@Data
public class Result<T> {
// 响应码
private int code;
// 状态信息
private String msg;
// 返回数据
private T data;
// 返回常量
private String result;

public Result() {

}

/**
* 异常构造方法
* @param exception
*/
public Result(Exception exception) {
this.result = WebConstants.RESULT_FAILED;
this.msg = exception.getMessage();
this.data = (T) exception.getMessage();
}

public Result(String message, Exception exception) {
this(exception);
this.msg = message;
}

public Result(String message, Exception exception, Boolean success) {
this(exception);
this.code = ResultCode.FAIL.code();
this.msg = message;
this.result = WebConstants.RESULT_SUCCESS;
}

public Result(String message, Exception exception, Integer code) {
this(exception);
this.msg = message;
}

public Result setCode(ResultCode resultCode) {
this.code = resultCode.code();
return this;
}

public int getCode() {
return code;
}

public T getData() {
return data;
}

public Result setData(T data) {
this.data = data;
return this;
}

public Result setCode(int code) {
this.code = code;
return this;
}

public String getMsg() {
return msg;
}

public Result setMsg(String msg) {
this.msg = msg;
return this;

}

public String getResult() {
return result;
}

public Result setResult(String result) {
this.result = result;
return this;
}
}

响应码枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误

private final int code;

ResultCode(int code) {
this.code = code;
}

public int code() {
return code;
}

}

返回常量

1
2
3
4
5
6
7
8
9
10
11
12
13
public class WebConstants {
// 返还成功
public static final String RESULT_SUCCESS = "success";
// 返还异常-业务异常
public static final String RESULT_FAILED = "fail";
// 返还失败-系统异常
public static final String RESULT_ERROR = "error";
// token
public static final String HEADER_TOKEN = "Atoken";
// Token验证失败,用户验证异常
public static final Integer AUTH_VALIDATE_FAILED = -1;
public static final String SERVICE_ERROR_TIPS = "服务内部异常,请稍后再试";
}
  • 时间格式化

    1
    2
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
  • 在数据库中不能为空的字段在ServiceImpl中使用 AssertUtils.notNull(字段,"参数不能为空")进行断言,参数为空抛出异常信息。

  • 调用过程:

    controller (dto)->service接口(dto)->serviceImpl实现类(dto):使用@Service注解->bl(dto)实现类:使用@Service注解->mapper(po)接口(对应mapper.xml文件)->mysql

  • 当前时间:

    1
    2
    3
    4
    5
    6
    7
    Timestamp timestamp = new Timestamp(System.currentTimeMillis());
    System.out.println(timestamp);// 2020-12-10 11:03:11.976
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String timestampDF = df.format(timestamp);
    System.out.println(timestampDF);// 2020-12-11 11:19:51
    Date parseDate = DateUtils.parseDate(timestampDF, "yyyy-MM-dd HH:mm:ss");
    System.out.println(parseDate);// Fri Dec 11 11:20:23 CST 2020

分页

分页结果集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PageList<T extends Serializable> implements Serializable {
private static final long serialVersionUID = -6177180431483012580L;
/**
* 返回的数据集合
*/
private List<T> dataList;

/**
* 返回的分页对象信息
*/
private Pager pager;

public List<T> getDataList() {
return dataList;
}

public void setDataList(List<T> dataList) {
this.dataList = dataList;
}

public Pager getPager() {
return pager;
}

public void setPager(Pager pager) {
this.pager = pager;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Pager extends PagerCondition {

private static final long serialVersionUID = 1L;

/**
* 总记录数
*/
private Integer recordCount;

/**
* 总页数
*/
private Integer totalPage;

public Pager() {
super();
}

public Pager(Integer currentPage, Integer pageSize, Integer recordCount) {
super(currentPage, pageSize);
this.recordCount = recordCount;
Integer totalPageSize = recordCount / pageSize;
Integer remailder = recordCount % pageSize;
// 如果总记录数与每页显示条数的余数大于0,总页数加1
if (remailder > 0) {
totalPageSize = totalPageSize + 1;
}
totalPage = totalPageSize;

}

public Pager(PagerCondition pageCondition, Integer recordCount) {
setCurrentPage(pageCondition.getCurrentPage());
setPageSize(pageCondition.getPageSize());
setRecordCount(recordCount);
setTotalPage((recordCount + getPageSize() - 1) / getPageSize());
}

public Integer getRecordCount() {
return recordCount;
}

public void setRecordCount(Integer recordCount) {
this.recordCount = recordCount;
}

public Integer getTotalPage() {
return totalPage;
}

public void setTotalPage(Integer totalPage) {
this.totalPage = totalPage;
}
}

分页结果集中的分页参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class PagerCondition implements Serializable {

private static final long serialVersionUID = 1L;

/**
* @Fields 当前页
*/
private Integer currentPage = 1;

/**
* @Fields 每页的数量
*/
private Integer pageSize = 20;

public PagerCondition() {
super();
}

public PagerCondition(Integer currentPage) {
super();

if (currentPage != null)
this.currentPage = currentPage;

this.pageSize = 20;
}

public PagerCondition(Integer currentPage, Integer pageSize) {
super();

if (currentPage != null)
this.currentPage = currentPage;

this.pageSize = pageSize;
}

public Integer getCurrentPage() {
return currentPage;
}

public Integer getLimitnum() {
return this.getPageSize();
}

public Integer getOffset() {
return (this.getCurrentPage() - 1) * this.getPageSize();
}

public Integer getPageSize() {
if (pageSize == null || pageSize <= 0)
return 20;
else
return pageSize;
}

public Integer getPreviousPage() {
if (this.currentPage > 0) {
return this.currentPage - 1;
} else {
return 0;
}
}

public void setCurrentPage(Integer currentPage) {
this.currentPage = currentPage;
}

public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}

public Integer startRow() {
int startRow = 0;
if (currentPage != 1) {
startRow = (currentPage - 1) * pageSize;
}
return startRow;
}
}

数据库查询返回分页结果集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 这里的Page是PageHelper内部定义的Page
public class PageResult<E extends Serializable> extends Page<E> {

/**
* 转换为PageList
* @return 转换结果
*/
public PageList<E> toPageList() {
final PageList<E> pageList = new PageList<>();
pageList.setPager(getPager());
pageList.setDataList(new ArrayList<>(this));
return pageList;
}

public Pager getPager() {
final Pager pager = new Pager();
pager.setPageSize(getPageSize());
pager.setCurrentPage(getPageNum());
pager.setRecordCount(Math.toIntExact(getTotal()));
pager.setTotalPage(getPages());
return pager;
}
}

并结合PageHelper实现分页,PageHelper分页的实现原理就是,通过PageHelper内部的拦截器在我们执行SQL语句之前动态的将SQL语句拼接分页的语句。

配置好PageHelper后,在每次查询前设置Page参数即可完成分页:

1
PageHelper.startPage(queryDTO.getCurrentPage(), queryDTO.getPageSize());

通过实现Mybatis拦截器实现分页参数的传递:

添加配置文件,将自定义的拦截器和pageHelper的拦截器配置到mybatis.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
<plugin interceptor="com.zyz.config.PageResultEnhancer"/>
</plugins>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageResultEnhancer implements Interceptor {

@Override
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
final Object result = invocation.proceed();
if (result instanceof Page) {
final Page page = (Page) result;
final PageResult pageResult = new PageResult();
pageResult.addAll(page);
pageResult.setPageNum(page.getPageNum());
pageResult.setPageSize(page.getPageSize());
pageResult.setStartRow(page.getStartRow());
pageResult.setEndRow(page.getEndRow());
pageResult.setTotal(page.getTotal());
pageResult.setPages(page.getPages());
pageResult.setCount(page.isCount());
pageResult.setOrderBy(page.getOrderBy());
pageResult.setOrderByOnly(page.isOrderByOnly());
pageResult.setReasonable(page.getReasonable());
pageResult.setPageSizeZero(page.getPageSizeZero());
return pageResult;
}
return result;
}

/**
* 只拦截Executor
*/
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}

@Override
public void setProperties(Properties properties) {
}
}

实现分页切面完成PageHelper分页参数的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
@Component
public class PageAspect {

//@Pointcut("execution(* com.shengsigu.service..*.*(..))")
@Pointcut("execution(* com.shengsigu.service..*.pageList*(..))")
public void pagePointcut(){}


@Before("pagePointcut()")
public void pageBefore(JoinPoint joinPoint) {
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();

// 获取到当前方法的方法签名, 判断返回类型是否是 PageList, 如果是则执行分页逻辑
boolean isPageList = method.getReturnType().equals(PageList.class);
if (isPageList) {
// 获取参数 currentPage, pageSize,并设置分页
Object[] args = joinPoint.getArgs();
PageCondition pageCondition = (PageCondition) args[0];
PageHelper.startPage(pageCondition.getPageNum(),pageCondition.getPageSize(),true);
}
}
}

反思

  • 不要想当然,凭主观判断别人写的方法,调用别人的方法的时候点进去看一看是否满足自己的需求。
  • 仔细检查!!! 做事情要有理有据,不要马虎!!!
  • 不要过分地参照原来的代码,它有可能也不规范!
  • 字段的意义?内部业务逻辑?多问写该代码的同事!
  • 对象调用方法前,记得考虑一下是否为空!
  • if-else语句中有重复的语句,提取出来放置if-else之外!
  • if里面有if,考虑合并判断条件?
  • 熟悉一块业务功能不能只看代码,应该还要去熟悉流程,数据库。
  • 永远不要相信前端传过来的参数是可靠,处于安全考虑,后端必须进行必要的参数校验。
  • 对象只有在需要时才创建,不要提前创建,避免浪费内存。
  • 最好使用空对象代替null返回给前端。
  • 删除、修改单个属性为一个常量时,前端传递一个id就可以了,不用传递整个实体对象。
  • 对象调用方法时,注意判空!
  • 方法(动词+名词)和变量(boolean类型 :is+形容词)做到见名知意。
  • mapper文件里面的sql尽量不要写死,使用接口传递常量值。
  • 一个方法中不同的功能模块用空行隔开。

Git

  • push代码时确保push到的远程分支是自己新建的一个分支,千万不要push到主分支中!!!有的项目可能没有设置权限,提交的代码就直接合并到主分支中参与构建了,最终导致构建失败!

  • push大量改动时,将改动拆分push,push前检查改动,以最小改动为基准,能添加尽量不要删除,一般情况不要合并其他环境的版本到当前环境的版本,dubbo cache不要勾选!

  • 确保本地编译成功再提交,pom文件的依赖不能随便加,但是要导的包一定记得导,导不进来的二方接口是别人的代码还没合,包还没构建的原因。

  • 提交前merge同一环境下的目标分支到自己本地的分支,不要与fetch更新自己远程的分支到本地分支弄混淆。

  • Git Log查看git操作的日志。

  • Undo Commit 撤销commit。

  • git pull强制覆盖:

    git fetch origin master 先把远程仓库最新状态拉到本地(以master为例);

    git reset --hard origin/master 将本地仓库重置到远程仓库最新状态(本地将被覆盖)。

    或者如果你在其他分支上:git reset --hard origin/<branch_name>

    git pull

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@SpringBootTest
class TestApplicationTests {
@Test
void testListToMap() {
List<User> users = new ArrayList<>();
users.add(new User("111", "zhangsan1"));
users.add(new User("222", "zhangsan2"));
users.add(new User("111", "zhangsan3"));
// 除去key重复的键值对,保留第一次出现的 返回指定类型的 map
Map<String, User> usersMap = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity(), (a, b) -> a, ConcurrentHashMap::new));
System.out.println(usersMap.getClass());
System.out.println(usersMap);
}

@Test
void testOptional() {
List<User> users = new ArrayList<>();
users.add(new User("111", "zhangsan1"));
users.add(new User("222", "zhangsan2"));
users.add(new User("333", "zhangsan3"));
users.add(null);
// findAny()操作,返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。
// 使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个
// 用来判断集合中是否有满足条件的元素,存在返回true,并且允许集合元素为null的情况
Optional<User> usersIsNull = users.stream().filter(e -> e != null && e.getName().equals("zhangsan")).findAny();
System.out.println(usersIsNull.isPresent());// false
}


@Test
public void testStringUtils() {
System.out.println(StringUtils.isNumeric("123"));// true
System.out.println("--------isEmpty-------");
// isEmpty判断空字符串时返回 false
System.out.println("null->" + StringUtils.isEmpty(null));// true
System.out.println("\"\"->" + StringUtils.isEmpty(""));// true
System.out.println("\" \"->" + StringUtils.isEmpty(" "));// false
System.out.println("--------isBlank-------");
System.out.println("null->" + StringUtils.isBlank(null));// true
System.out.println("\"\"->" + StringUtils.isBlank(""));// true
System.out.println("\" \"->" + StringUtils.isBlank(" "));// true
}

@Test
public void testAssert() {
//Assert.notNull(null,"判断null");// 抛出异常
//Assert.notNull("","判断\"\"");// 不会抛出异常
//Assert.notNull(" ","判断null\" \"");// 不会抛出异常
//Assert.notNull(new ArrayList<>());// 不会抛出异常
User user = new User();
}

@Test
public void test(){
Integer num = Integer.valueOf("");// 抛出 java.lang.NumberFormatException
}

@Test
public void testUUID(){
// 生成19位随机数 直接使用 在多线程环境下可能重复 写成工具类方法并用synchronized关键字修饰
Long shortUuid = UUID.randomUUID().getLeastSignificantBits() * -1;
System.out.println(shortUuid);
// 生成uuid去掉-,可与redis的key拼接 生成唯一的键
String uuid = UUID.randomUUID().toString().replace("-","");
System.out.println(uuid);
}

@Test
public void testMap(){
List<User> users = new ArrayList<>();
ArrayList<String> study = new ArrayList<>();
ArrayList<String> sports = new ArrayList<>();
study.add("chinese");
study.add("math");
study.add("english");
sports.add("dance");
sports.add("basketball");
users.add(new User("111", "zhangsan1",new Hobby(study,sports)));
users.add(new User("222", "zhangsan2",new Hobby(study,sports)));
users.add(new User("333", "zhangsan3",new Hobby(study,sports)));

List<String> ids = users.stream().map(User::getId).collect(Collectors.toList());
System.out.println(ids);// [111, 222, 333]
// flatmap 将对象中的属性转换为流,方便进行二次处理
List<String> usersports = users.stream().flatMap(user -> user.getHobby().getSports().stream()).collect(Collectors.toList());
System.out.println(usersports);// [dance, basketball, dance, basketball, dance, basketball]
// groupingBy分组 封装成为 Map<指定属性,List<User>> 指定属性相同的 User在一个List里面
Map<String, List<User>> mapById = users.stream().collect(Collectors.groupingBy(User::getId));
System.out.println(mapById);
// toMap 封装成 Map<指定属性,User> 指定属性重复时抛 java.lang.IllegalStateException
Map<String, User> mapById2 = users.stream().collect(Collectors.toMap(User::getId, Function.identity()));
System.out.println(mapById2);
}

@Test
public void testSingletonList(){
/**
* Arrays.asList(strArray)返回的是一个可变的集合,
* 但是返回值是其内部类,不具有add方法,可以通过set方法进行增加值,默认长度是10
*
*
* Collections.singletonList()返回的是不可变的集合,但是这个长度的集合只有1,可以减少内存空间。
* 但是返回的值依然是Collections的内部实现类,同样没有add的方法,调用add,set方法会报错
*/

User user = new User("111", "zhangsan1");
List<User> oneToList1 = Collections.singletonList(user);
// oneToList1.add(new User("222","zyz"));// java.lang.UnsupportedOperationException
System.out.println(oneToList1);
List<User> oneToList2 = Stream.of(user).collect(Collectors.toList());
oneToList2.add(new User("222","zyz"));
System.out.println(oneToList2);
}

@Test
public void testDate() throws ParseException {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
System.out.println(timestamp);// 2020-12-10 11:03:11.976
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timestampDF = df.format(timestamp);
System.out.println(timestampDF);// 2020-12-11 11:19:51
Date parseDate = DateUtils.parseDate(timestampDF, "yyyy-MM-dd HH:mm:ss");
System.out.println(parseDate);// Fri Dec 11 11:20:23 CST 2020
}

@Test
public void testNumeric(){
System.out.println(StringUtils.isNumeric("123"));// true
System.out.println(StringUtils.isNumeric("12.3"));// false
System.out.println(StringUtils.isNumeric(""));// false
System.out.println(StringUtils.isNumeric(" "));// false
}
}

业务

  • 在实际开发中,添加和更新功能可以写在一起,添加时前端不传id,更新时前端传id。

  • 对前端传过来的json通过@RequestBody转换为JavaBean后进行非空判断

    • 经过@RequestyBody封装的javaBean一定不为null,只需进行非空判断,或者对其属性进行非空判断
    • 一些必须传的数据可使用断言判断,截断流程
    • if判断,在保证不阻断业务流程下使用
    • 集合元素进行forEach遍历之前,必须进行非空判断CollectionUtils.isEmpty(collection)
  • 修改密码的逻辑

    • 在根据原密码修改密码,验证原密码,验证密码时需要对原密码进行加密再与数据库中的密码进行比较:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 传入参数:旧密码oldPassword、新密码newPassword、用户id userId 
    String digestOldPassword = DigestUtils.md5DigestAsHex(oldPassword.getBytes());
    if (!digestOldPassword.equals(user.getPassword().trim().toLowerCase())) {
    new RuntimeException("输入的原密码错误!");
    }
    // 密码强度校验......
    // 对新密码进行加密
    String digestNewPassword = DigestUtils.md5DigestAsHex(newPassword.trim().getBytes());
    // 更新密码到数据库
  • 枚举类的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public enum ChargeOffStateEnum {
/**
* 审核中
*/
WAIT_AUTID((byte) 1, "审核中"),
/**
* 已审核
*/
CANCEL((byte) 2, "已取消"),
/**
* 已取消
*/
COMPLETE((byte) 3, "已完成");

ChargeOffStateEnum(Byte value, String name) {
this.name = name;
this.value = value;
}

private Byte value;
private String name;

public Byte getValue() {
return value;
}

public String getName() {
return name;
}

public static ChargeOffStateEnum getChargeOffStateEnum(Byte value) {
if (value == null) {
return null;
}
ChargeOffStateEnum[] stateEnums = ChargeOffStateEnum.values();
for (ChargeOffStateEnum stateEnum : stateEnums) {
if (value.equals(stateEnum.getValue())) {
return stateEnum;
}
}
return null;
}
}

bug处理

定位bug: