后端开发实习积累

文章目录

  • 一、字符串相关技巧
  • 二、数组、集合相关技巧
  • 三、Java 基础、jdk8新特性
  • 四、数据库相关
  • 五、GSON使用
  • 六、Spring、SpringBoot相关
  • 七、时间转换
  • 八、Linux相关
  • 九、Redis相关
  • 十、消息队列相关
  • 十一、Thrift
  • 十二、Vim常用操作
  • 十三、Git相关
  • 十四、测试
  • 十五、设计模式
  • 十六、好用的插件

一、字符串相关技巧

1、StringUtils

  • 判断字符串是否为空?
     其中是指字符串为null、空字符串或都是空白(空格等)
     maven需要引入依赖:commons-lang3
		<dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.7version>
        dependency>
package com.hfut;
import org.apache.commons.lang3.StringUtils;
public class test {
    public static void main(String[] args) {
        String nullStr = null;
        String emptyStr = "";
        String spacesStr = "  ";
        String tabStr = "\t";
        String str = "123";
        System.out.println(StringUtils.isBlank(nullStr));	//true
        System.out.println(StringUtils.isBlank(emptyStr));	//true
        System.out.println(StringUtils.isBlank(spacesStr));	//true
        System.out.println(StringUtils.isBlank(tabStr));	//true
        System.out.println(StringUtils.isBlank(str));		//false
    }
}
  • 连接String、Integer、Long等Object
import com.google.common.base.Joiner;
StringUtils.joinWith(",", myStr, myInteger, myLong)
List<Long> tag = Arrays.asList(1L,2L,3L);
String str = Joiner.on(",").join(tag);

2、Google Guava库

 	<dependency>
        <groupId>com.google.guavagroupId>
        <artifactId>guavaartifactId>
        <version>21.0version>
    dependency>

1)参数校验

import com.google.common.base.Preconditions;
Preconditions.checkArgument(!CollectionUtils.isEmpty(positionList),"资源位置信息参数不能为空");
Preconditions.checkNotNull(userId, "获取用户开户信息请求参数用户Id不能为空");

3、aviator 表达式解析包
Aviator依赖了commons-beanutils, 使用Aviator可以添加下面的maven依赖:

<dependency>
<groupId>com.googlecode.aviatorgroupId>
<artifactId>aviatorartifactId>
<version>2.3.3version>
dependency>
import com.googlecode.aviator.AviatorEvaluator;
Map<String, Object> expressionParams = Maps.newHashMapWithExpectedSize(6);
Object expressionRet = AviatorEvaluator.execute(expression, expressionParams);

Java 进阶 & Aviator 表达式的使用

二、数组、集合相关技巧

1、数组和List转换
要想把基本数据类型的数组转化为其包装类型的list,可以使用guava类库的工具方法,示例如下:

int[] intArray = {1, 2, 3, 4};
List<Integer> list = Ints.asList(intArray);

转换后的list为定长list,不能添加和删除会报错。
2、判断集合是否为空
其中指null或empty

import org.springframework.util.CollectionUtils;
if (CollectionUtils.isEmpty(myList)) {
	return Collections.emptyList();
}

判断Map是否为空:

import org.apache.commons.collections.MapUtils;
if (MapUtils.isNotEmpty(myMap))

3、判断两个集合是否有交集

CollectionUtils.containsAny(Collection<?> source, Collection<?> candidates)

4、Collectors.groupingBy
5、集合排序,先按…排后按…排

myList.sort(Comparator.comparing(ResourceDTO::getPosition)
			.thenComparing()
			.thenComparing(Comparator.comparing(...).reversed()));

6、返回空集合
mymap = Collections.emptyMap();

三、Java 基础、jdk8新特性

1、stream使用

mylist.stream().collect(Collectors.toMap(Mybo::getId, p -> p, (a,b) -> a));

Java 8 Stream
Java流(Stream)操作实例-筛选、映射、查找匹配
2、反射的应用

Field dataField = t.getField("data");
dataField.setAccessible(true);
dataField.set(response, data);

3、Java的三种标准注解,四种元注解:

  • 三种标准注解:@Overrid、@Deprecated、@SuppressWarnings
  • 四种元注解:@Target、@Retention、@Documented、@Inherited

@Inherited用来修饰注解:被元注解Inherited修饰的注解,只有作用在类上时,会被子类继承此自定义的注解,其余情况都不会继承
Java中三种标准注解和四种元注解
4、Predicate
Predicate详解
5、构造函数,@Autowired,@PostConstruct的执行顺序为:
构造函数 > @Autowired > @PostConstruct
6、Java8新特性:时间API

  • Instant:代表的是时间戳。
  • LocalDate:不包含具体时间的日期。
  • LocalTime:不含日期的时间。
  • LocalDateTime:包含了日期及时间。
    JDK8 新增的日期时间API

四、数据库相关

1、分页接口 pagehelper

import com.github.pagehelper
@Autowired
private IMService mService;
PageInfo<MDO> pageInfo = mService.queryListByPage(int id, int pageNum, int pageSize);
List<MDO> res = pageInfo.getList(); // 该List为最终的该页查询出的结果

IMService接口实现:

@Override
    public PageInfo<MDO> queryListByPage(int id, int pageNum, int pageSize) {
        PageMethod.startPage(pageNum, pageSize);
        List<MDO> myList = myMapper.queryList(id);
        return new PageInfo<>(myList);
    }

2、主从复制、读写分离
读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
【mysql 读写分离】10分钟了解读写分离的作用
3、Mybatis常用语法
1)去除多余的逗号:

<trim prefix="(" suffix=")" suffixOverrides=",">
	<if test="position != null">
        #{position,jdbcType=INTEGER},
    if>
    <if test="title != null">
        #{title,jdbcType=VARCHAR},
    if>
trim>

此外 标签也可以自动去除多余的逗号、AND 、OR
2)循环语法

and `position` in
      <foreach item="item" collection="posIdList" separator="," open="(" close=")" index="">
        #{item}
      foreach>

3)尽量用>或>=,不用转义。

原有符号 mybatis中
< <
> >
& &
'
" "
!= ]]>

4)insert时使用自增主键:useGeneratedKeys=“true”
5)select时尽量用:resultMap=“BaseResultMap”
6)BaseResultMap、数据库所有列写法:

<resultMap id="BaseResultMap" type="com.sankuai.fortune.marketing.domain.UserMarketingDO">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <result column="activity_id" jdbcType="BIGINT" property="activityId"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
        <result column="try_times" javaType="INTEGER" property="tryTimes"/>
resultMap>
<sql id="Base_Column_List">
        id,
        involved_in_id,
        user_id,
        activity_id,
        status,
        error_code,
        create_time,
        update_time,
        try_times
sql>

搜索所有列是时可以:select from user_marketing where user_id = #{userId,jdbcType=BIGINT}
7)LIKE语法:
title like concat('%',#{title},'%')
8)if…else…语法:

<choose>
    <when test="">
        //...
    when>
    <otherwise>
        //...
    otherwise>
choose>

4、数据库建表时推荐字符集:
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
5、生产全局唯一ID:Leaf
Leaf——美团点评分布式ID生成系统
美团Leaf源码——snowflake模式源码解析

五、GSON使用

1、Json -> Object

new Gson().fromJson(value, type);

Gson的fromJson()方法

2、GsonUtil.toJson(userTags)

public static String toJson(Object object) {
        return (new GsonBuilder()).serializeNulls().disableHtmlEscaping().create().toJson(object);
    }

3、获取类型
Type type = new TypeToken>() {}.getType();
Type setType = new TypeToken>(){}.getType();
4、创建GsonBuilder

Gson gson = new GsonBuilder()
                .setDateFormat("yyyy-MM-dd HH:mm:ss")
                .create();

六、Spring、SpringBoot相关

1、yml配置多个环境
SpringBoot多环境yml文件配置
2、实现自定义注解
Spring 自定义注解
深入理解Java:注解(Annotation)自定义注解入门
3、实现ApplicationContextAware接口

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }
    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }
    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }
    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}

当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以很方便地从已有的spring上下文取得已实例化的bean。换句话说,就是这个类可以直接获取spring配置文件中,所有引用到的bean对象。
ApplicationContextAware使用理解

4、利用注释和AOP实现日志打印
根据切点获取注释:

private MonitorLog getMonitorLog(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        //获取方法的自定义注释
        MonitorLog monitorLog = method.getAnnotation(MonitorLog.class);
        if (monitorLog != null) {
            return monitorLog;
        }
        //获取类的自定义注释
        MonitorLog classMonitorLog = pjp.getTarget().getClass().getAnnotation(MonitorLog.class);
        if (classMonitorLog != null) {
            return classMonitorLog;
        }
        return null;
    }
printReqLog(pjp, monitorLog);
Object ret = pjp.proceed();
printResLog(pjp, monitorLog, ret);
return ret;
@Pointcut("@annotation(com.hfut.shuili.common.annotation.MonitorLog)")
    public void pointcut() {
    }
    
@Around("!(execution(* com.hfut.shuili.thrift..*.*(..))) && pointcut() && @annotation(monitorLog)")
public Object monitorProceeding(ProceedingJoinPoint pjp, MonitorLog monitorLog) throws Throwable {
        String simpleName = pjp.getTarget().getClass().getSimpleName();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        try {
        	//方法执行前
            printReqLog(pjp, monitorLog);
            //执行方法
            Object ret = pjp.proceed();
            //方法执行后
            printResLog(pjp, monitorLog, ret);
            return ret;
        } catch (Throwable e) {
            Cat.logEvent("ServiceException", simpleName.concat(".").concat(signature.getName()));
            throw e;
        }
    }

@Around简单使用示例——SpringAOP增强处理
后端开发实习积累_第1张图片
5、定时任务
使用spring提供的@Scheduled实现 SpringBoot使用@Scheduled创建定时任务

七、时间转换

1、long时间戳与Date转换

import java.util.Date;

public class DateUtil extends cn.hutool.core.date.DateUtil {
    /**
     * 时间戳转date
     * @param date
     * @return
     */
    public static Date parseFortuneTime(long date){
        return DateUtil.date(date);
    }
    /**
     * 转时间戳(毫秒)
     * @param date
     * @return
     */
    public static Long formatFortuneTime(Date date){
        if (date != null){
            return date.getTime();
        }
        return null;
    }
    //获取某天0点
    public static Date getStartTimeOfDay(Date date) {
        Calendar day = Calendar.getInstance();
        day.setTime(date);
        // 24小时制度
        day.set(Calendar.HOUR_OF_DAY, 0);
        day.set(Calendar.MINUTE, 0);
        day.set(Calendar.SECOND, 0);
        day.set(Calendar.MILLISECOND, 0);
        return day.getTime();
    }
    //获取年月日
    public static Date timeToDate(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String dateStr = simpleDateFormat.format(date);
        try {
            return simpleDateFormat.parse(dateStr);
        } catch (ParseException e) {
            throw new ServiceException(ErrorEnum.SYSTEM_INNER_ERROR);
        }
    }
}
import cn.hutool.core.date.DateTime;
import java.util.Calendar;

DateTime now = DateUtil.date();	//获取当前时间
Calendar day = Calendar.getInstance();
day.setTime(date);

引入Hutool:

<dependency>
    <groupId>cn.hutoolgroupId>
    <artifactId>hutool-allartifactId>
    <version>4.5.15version>
dependency>

毫秒时间戳(13位)转秒时间戳(10位)

long timeStampSec = System.currentTimeMillis()/1000;
String timestamp = String.format("%010d", timeStampSec);2017-02-27 15:48:2713位时间戳为:1488181707000

System.out.println(String.format("%010d",1488181707000L/1000));
输出:1488181707

时间格式:

SimpleDateFormat df = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);	//设置日期格式
System.out.println(df.format(new Date()));	// new Date()为获取当前系统时间

某日加几天:

import org.apache.commons.lang.time.DateUtils;
//startDate + x 天
Date endDate = DateUtils.addDays(startDate, x);

Hutool 参考文档
时间戳与时间转换网站

八、Linux相关

1、查看日志的Linux指令

  • 查看request.log的后300行并实时更新:tail request.log -n 300 -f
  • 查看error.log中包含"timed out"的行号:cat error.log -n | grep "timed out"
  • 查看error.log中的95-98行:sed -n '95,98p' error.log
  • 查看error.log中包含“Exception”的那一行的上下10行(C),并取结果的后20行:grep -n -C10 'Exception' error.log | tail -n 20
  • 查看request.log中包含method或userid的那几行: grep -E "method | userid" request.log --color
  • 查看request.log中同时包含method+任意字符+userid的那几行 :grep -E "method.*userid" request.log --color
  • 统计error.log中出现1234的次数: grep -c '1234' error.log
  • 查看指定文件的行数:wc -l a.txt

linux查看日志方法
grep -v、-e、-E

九、Redis相关

1、Redis持久化策略:RDB(数据快照)、AOF(文件重写)。两者都开启的话会优先使用AOF,因为AOF的完整性更高。执行持久化时是通过主进程fork子进程的方式实现,然后把持久化的工作交给子进程,自己不会有相关的 I/O 操作。Linux fork 子进程采用的是 copy-on-write 的方式
2、常用数据结构:String、List、Hash、Set、SortedSet、HyperLogLog、位图、地理信息定位
3、大key拆分:

  • 可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;或者转成hash存储。

  • hash, set,zset,list 中存储过多的元素。
    以hash为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)
    现在,固定一个桶的数量,比如 10000, 每次存取的时候,先在本地计算field的hash值,模除 10000, 确定了该field落在哪个key上。

    newHashKey = hashKey + ( hash(field) % 10000); hset (newHashKey, field, value) ; hget(newHashKey, field)
    4、使用Redis实现分布式锁:
    SET key value NX EX 30
    分布式锁用 Redis 还是 Zookeeper? 什么是分布式锁
    可以用Redission实现分布式锁,又看门狗功能,每10s检查下当前线程没执行完毕,就再把过期时间延长30s。
    5、Redis删除过期键的策略:
    常见的删除策略:
    (1)立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。
    (2)惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。
    (3)定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。
    redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用。

十、消息队列相关

1、使用场景:异步、削峰、解耦。
2、MQ可能产生的问题:重复消费、消息丢失、顺序消费
3、主流的消息队列中间件主要有:KafkaRocketMQ、ActiveMQ、RabbitMQ
参考:敖丙:消息队列
4、zookeeper启动命令:

  • 修改zookeeper配置文件,设置dataDir、dataLogDir:vim apache-zookeeper-3.6.1-bin/conf/zoo.cfg
  • 开启服务端:zkServer.sh start
  • 开启客户端:zkCli.sh -server 127.0.0.1:2181
  • 关闭服务端:zkServer.sh stop

5、kafka启动命令:

  • 先开启zookeeper服务端:zkServer.sh start
  • 初次启动需修改配置文件:vim kafka_2.12-2.5.0/config/server.properties
//有几个broker就设置几个broker.id
broker.id=0
listeners = PLAINTEXT://localhost:9092
log.dirs=/Users/mac/Documents/kafka/log
zookeeper.connect=localhost:2181
  • kafka-server-start.sh config/server.properties
  • 使用JPS查看kafka是否启动成功:
jps -l
20438 org.apache.zookeeper.server.quorum.QuorumPeerMain
21725 sun.tools.jps.Jps
20446 kafka.Kafka

6、创建主题:

  • 创建一个副本因子为1,分区为4的主题,名为topic-demo:
    kafka-topics.sh --zookeeper localhost:2181 --create --topic topic-demo --replication-factor 1 --partitions 4
  • 查看分区详情:kafka-topics.sh --zookeeper localhost:2181 --describe --topic topic-demo

7、订阅消息
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic topic-demo
8、生产者发送消息

  • kafka-console-producer.sh --broker-list localhost:9092 --topic topic-demo
  • > hello,kafka
  • 在消费者的终端里可以看到这条消息
Topic: topic-demo	PartitionCount: 4	ReplicationFactor: 1	Configs: 
	Topic: topic-demo	Partition: 0	Leader: 0	Replicas: 0	Isr: 0
	Topic: topic-demo	Partition: 1	Leader: 0	Replicas: 0	Isr: 0
	Topic: topic-demo	Partition: 2	Leader: 0	Replicas: 0	Isr: 0
	Topic: topic-demo	Partition: 3	Leader: 0	Replicas: 0	Isr: 0

9、生产者投送保障:
1⃣️At most once:消息可能会丢,但绝不会重复传输,客户端收到消息后,在处理消息前自动提交,这样kafka就认为consumer已经消费过了,偏移量增加。
2⃣️At least one:消息绝不会丢,但可能会重复传输,这样就可能出现消息处理完了,在提交反馈前,网络中断或者程序挂了,那么kafka认为这个消息还没有被consumer消费,产生重复消息推送。
3⃣️Exactly once:每条消息肯定会被传输一次且仅传输一次;
10、为了使得 Kafka 的吞吐率可以线性提高,物理上把 Topic 分成一个或多个 Partition。即同一个partition内的消息只能被同一个组中的一个consumer消费。当消费者数量多于partition的数量时,多余的消费者空闲
11、生产者产生消息的路由策略:

  1. 指定了 patition,则直接使用;
  2. 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition
  3. patition 和 key 都未指定,使用轮询选出一个 patition。

12、删除消息策略:
对于传统的 message queue 而言,一般会删除已经被消费的消息,而 Kafka 集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此 Kafka 提供两种策略删除旧数据。一是基于时间,二是基于 Partition 文件大小。例如可以通过配置 $KAFKA_HOME/config/server.properties,让 Kafka 删除一周前的数据,也可在 Partition 文件超过 1GB 时删除旧数据。这里要注意,因为 Kafka 读取特定消息的时间复杂度为 O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。
13、zookeeper:JavaEE进阶——ZooKeeper和Curator
14、Mafka是基础架构-MQ团队从2016年开始自研的消息队列产品,底层基于Apache Kafka,增加了自研的基于机房粒度的中心化调度、时间回溯、粘性分配、死信、延迟队列、适用于美团自用的同步/异步客户端、机房容灾等高阶特性,目前版本是3.0。

十一、Thrift

1、基本类型 (括号内为对应的Java类型)

  • bool(boolean): 布尔类型(TRUE or FALSE)
  • byte(byte): 8位带符号整数
  • i16(short):16位带符号整数
  • i32(int): 32位带符号整数
  • i64(long): 64位带符号整数
  • double(double):64位浮点数
  • string(String): 采用UTF-8编码的字符串
  • binary:二进制数据

注意,thrift不支持无符号整型,因为很多目标语言不存在无符号整型
2、定义结构体:

struct DictionaryRes{
        /**
       * @FieldDoc(description = "状态")
       **/
        1: required Common.Status status;
        /**
        * @FieldDoc(description = "错误描述信息")
        **/
        2: optional Common.ErrorDTO error;
        /**
        * @FieldDoc(description = "字典数据")
        **/
        3: optional list<DictionaryDTO> data;

}

3、判断thrift里的变量是否被设置了

public boolean isSetId() {
    return __isset_bit_vector.get(__ID_ISSET_ID);
  }

4、Thrift调用外部服务
remoteAppKey为服务的app.name

@ThriftClientProxy(remoteAppKey = "com.hfut.ms.product", filterByServiceName = true, enableSignHandler = true)
private IProductService.Iface productService;

十二、Vim常用操作

  • :wq 保存并退出
  • $ 移动当前行行尾
  • ^或者0 移动到当前行行首
  • gg 整个文档的首部
  • w 下一个单词的首字母位置
  • b 上一个单词的首字母位置
  • G 文档最后一行
  • 将光标移动到要复制的文本开始的地方,按v进入可视模式。将光标移动到要复制的文本的结束的地方,按y复制。此时vim会自动将光标定位到选中文本的开始的地方,并退出可视模式。移动光标到文本结束的地方,按p粘贴。

十三、Git相关

  • git status 查看当前工作区情况
  • git add -A 提交所有变化
  • git add -u 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new)
  • git add . 提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件
  • git commit -am “coment” 提交代码
  • git reset HEAD XXX/XXX/XXX.java 对add的某个文件取消add
  • git reset HEAD . 对所有add的文件取消add
  • git reset --soft HEAD^ 撤销上次的commit
  • git reset --hard HEAD^ 撤销上次的commit和add(添加的文件直接消失,慎用!)
  • git reset --soft HEAD^^ 或 git reset --soft HEAD~2 撤销最近2次的commit
  • git checkout - - xxx.java表示撤销对某个文件的修改(慎用!其中两个杠中间无空格
  • git checkout . 放弃所有修改(慎用!)

十四、测试

1、Mockito
Mockito 简明教程
https://www.jianshu.com/p/eae0187900f8
2、Mock步骤:

  • @Mock注解一个service
import org.mockito.Mock;
import org.junit.Before;

@Mock
private IUserProfileService.Iface userProfileService;
  • 使用ReflectionTestUtils.setField给对象中的private变量注入依赖
@Before
public void before() {
    ReflectionTestUtils.setField(userProfileRemoteService,"userProfileService",userProfileService);
}
  • 编写mock函数
private void mockGetUserProfileResult(String city) throws TException {
	UserProfileResult userProfileResult = new UserProfileResult();
	userProfileResult.setStatuCode(0);
	BaseInfo baseInfo = new BaseInfo().setCity(city);
	userProfileResult.setData(new ResultData().setBaseInfo(baseInfo));
	//不管userProfileService.getUserProfileResult函数传入什么参数,一律都返回userProfileResult
	Mockito.when(userProfileService.getUserProfileResult(Mockito.anyString(),Mockito.any(UserFields.class))).thenReturn(userProfileResult);
 }

使用ReflectionTestUtils解决依赖注入
3、测试类上常用的注解:

@ActiveProfiles("local")	//选定配置文件
@RunWith(SpringRunner.class)	//让测试运行于Spring测试环境
@Rollback	//回滚
@Transactional	//spring事务
@SpringBootTest
  • @EnableTransactionManagement 注解可以启用事务管理功能
  • @Rollback 回滚,这样避免测试数据污染数据库,而且还可以利用测试数据重复测试,数据并不会真的更新到数据库中。
  • 直接在测试类上面加上如下2个注解@RunWith(SpringRunner.class)、@SpringBootTest就能取到spring中的容器的实例,如果配置了@Autowired那么就自动将对象注入。

4、使内存数据库:H2

		<dependency>
            <groupId>com.h2databasegroupId>
            <artifactId>h2artifactId>
            <version>1.4.200version>
            <scope>testscope>
        dependency>

test/resources/sql下有ms-ddl.sql、ms-dml.sql

@Configuration
@MapperScan("com.hfut.Msproject.mapper")
public class DbConfig {
    @Bean(name="dataSource")
    public DataSource dataSource() throws SQLException {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:sql/ms-ddl.sql")
                .addScript("classpath:sql/ms-dml.sql")
                .build();
    }
}

十五、设计模式

1、策略模式
注解中的变量作为key,对应的策略作为value:

//获取<枚举常量值, 策略>
private Map<Integer, MyStrategy> myStrategyMap = Maps.newHashMap();
//获取<"策略名", 策略>
Map<String, MyStrategy> beansWithAnnotationMap = SpringUtil.getApplicationContext().getBeansOfType(MyStrategy.class);
Collection<MyStrategy> strategies = beansWithAnnotationMap.values();
for (MyStrategy strategy : strategies) {
	MyAnnotation annotation = strategy.getClass().getAnnotation(MyAnnotation.class);	//获取注释
	Preconditions.checkArgument(annotation != null, strategy.getClass().getName() + "未至指定MyAnnotation注解");
	MyEnum[] categories = annotation.categories();	//获取注释中的参数值
	for (MyEnum categoryEnum : categories) {
		myStrategyMap.put(categoryEnum.getValue(), strategy);
	}
}

其中MyAnnotation为自定义的注释。myStrategyMap即为最终的策略对应map

十六、好用的插件

1、idea 插件 free mybatis plugin
2、Lombok
3、Thrift support

你可能感兴趣的:(Java)