数据库
【强制】主键索引名为pk_字段名;
唯一索引名为uk_字段名;
普通索引名则为idx_字段名。
【强制】表必备三字段: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
4public 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 })
- 默认发生
Error
或RuntimeException
时才会回滚,发生非运行时异常时不会回滚,需要使用rollbackFor
属性指定回滚的非运行时异常。
Controller中的Model参数
- 只有在前端请求有页面返回的时候用到,保存了想要展示在页面上的数据。
- 例如数据的导出时,使用到了Model参数。
自定义注解中的元注解
1 | // 定义注解使用的时机 METHOD->适用于方法 TYPE->适用于 class,interface,enum |
全局异常处理
对类添加@ControllerAdvice
注解,在处理异常的方法前添加
1 | (HttpStatus.OK) |
CommandLineRunner接口
在应用初始化后,去执行一段代码块逻辑,这段初始化代码在整个应用生命周期内只会执行一次。
用于预先加载数据,实现CommandLineRunner
接口,需要加载的数据的逻辑写在run()
方法中。
可使用@Order(1)
,@Order(2)
……注解指定加载顺序。
对象的有状态与无状态
是否存储数据
无状态的Bean适合用单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。
Controller中的返回数据
ResponseAdvisor
对返回数据进行了拦截,然后统一处理,所有的controller只需要返回业务相关数据即可。
1 | // 对controller的全局配置 |
RetResponse
对controller返回的业务数据封装成自定义的 Result对象返回给前端
1 | /** |
Result
自定义结果集封装对象
1 |
|
响应码枚举类
1 | public enum ResultCode { |
返回常量
1 | public class WebConstants { |
时间格式化
1
2"yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") (pattern =
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
7Timestamp 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 | public class PageList<T extends Serializable> implements Serializable { |
1 | public class Pager extends PagerCondition { |
分页结果集中的分页参数:
1 | public class PagerCondition implements Serializable { |
数据库查询返回分页结果集:
1 | // 这里的Page是PageHelper内部定义的Page |
并结合PageHelper实现分页,PageHelper分页的实现原理就是,通过PageHelper内部的拦截器在我们执行SQL语句之前动态的将SQL语句拼接分页的语句。
配置好PageHelper后,在每次查询前设置Page参数即可完成分页:
1 | PageHelper.startPage(queryDTO.getCurrentPage(), queryDTO.getPageSize()); |
通过实现Mybatis拦截器实现分页参数的传递:
添加配置文件,将自定义的拦截器和pageHelper的拦截器配置到mybatis.xml中
1 |
|
1 | .class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) ( (type = Executor |
实现分页切面完成PageHelper分页参数的设置:
1 |
|
反思
- 不要想当然,凭主观判断别人写的方法,调用别人的方法的时候点进去看一看是否满足自己的需求。
- 仔细检查!!! 做事情要有理有据,不要马虎!!!
- 不要过分地参照原来的代码,它有可能也不规范!
- 字段的意义?内部业务逻辑?多问写该代码的同事!
- 对象调用方法前,记得考虑一下是否为空!
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 |
|
业务
在实际开发中,添加和更新功能可以写在一起,添加时前端不传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 | public enum ChargeOffStateEnum { |
bug处理
定位bug: