前言
主要描述为了解决表的数据量比较大的时候,按天统计(按月或者按周同逻辑)一个表里面的多个类型做一个类型统计到另一张表,以方便升效率。
例如:表(例如消费记录)的某个字段 type(有多个类型):1:买菜 2:买肉 3:买海鲜 4. 买家具等等这样
原表(当数据大于几W多的时候或者亿W多的时候,查询运营在后台查询很慢,等待的时间较长,虽然有设置时间三个月内去查询,所以运营人员提出要做一次统计,这样他门就不用耗费太多时间去三个月内去查询并手动统计,所以这次的做法是将原表的数据统计沉淀在新表里面,并对某消费记录类型做按天统计。
CREATE TABLE `order_deatil` (
`id` bigint(20) NOT NULL,
`order_no` varchar(64) DEFAULT NULL COMMENT '订单详情id',
`pay_member_id` varchar(64) DEFAULT NULL COMMENT '消费的用户',
`pay_member_no` varchar(32) DEFAULT NULL COMMENT '消费会员的id',
`pay_type` varchar(16) DEFAULT NULL COMMENT '消费类型',
`consume_code` varchar(128) DEFAULT NULL COMMENT '消费功能编码',
`create_date` datetime(3) DEFAULT NULL COMMENT '创建日期',
`del_flag` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单详情表';
新表(把原表要统计的类统计到新表上,便于快速查询)
CREATE TABLE `order_deatil_statistics_daily` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '数据ID',
`buy_vegetables` int(11) DEFAULT '0' COMMENT '买菜',
`buy_meat` int(11) DEFAULT '0' COMMENT '买肉',
`buy_seafood` int(11) DEFAULT '0' COMMENT '买海鲜',
`buy_furniture` int(11) DEFAULT '0' COMMENT '买家具',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`update_date` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='每天统计订单详情表';
一、涉及技术点和注意点
- 技术: Springboot + mybatis + mysql + quartz + Swagger
- 设计模式:策略模式
- 定时任务:应该要每天凌晨或什么时候统计(注意有没有跨天的问题)
- 按天查询,每次分页查询 例如:每页500条或更少,以此来减少对数据查询的压力(必要时候还要加上索引等)
二、搭建项目
简单搭建:Springboot + mybatis + mysql + quartz +Swagger
(1).附上pom.xml
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-amqp
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.0
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.apache.commons
commons-lang3
3.9
commons-collections
commons-collections
3.2.1
org.projectlombok
lombok
true
(2).application.yml配置
server:
port: 8083
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://(ip地址):3306/(数据库名称)?characterEncoding=utf-8&useSSL=false
username: 数据账号
password: 数据密码
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.mi.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置mybatis log plugin
map-underscore-to-camel-case: true
logging:
level:
com: debug
(3). SpringbootApplication 启动类
@SpringBootApplication
@MapperScan("com.xx.xx")//使用MapperScan批量扫描所有的Mapper接口;
public class SpringbootDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemo1Application.class, args);
}
/**
* 需要加上这个上下文,以便后续使用策略模式的时候通过对应的容器加载对应的类
* @return
*/
@Bean
public SpringContextHolder springContextHolder() {
SpringContextHolder springContextHolder = new SpringContextHolder();
return springContextHolder;
}
}
三、config 配置包下准备
(1).SwaggerConfig (暂未体现用上)
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.pathMapping("/")
.select()
.apis(RequestHandlerSelectors.basePackage("com.mi.controller"))
.paths(PathSelectors.any())
.build().apiInfo(new ApiInfoBuilder()
.title("SpringBoot API 文档")
.description("SpringBoot整合Swagger,详细信息......")
.version("9.0")
.build());
}
}
(2). QuartzConfig
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
@Slf4j
public class QuartzConfig {
@Autowired
private OrderDeatilDao orderDeatilDao;
@Autowired
private OrderDeatilStatisticsDailyService orderDeatilStatisticsDailyService;
@Autowired
private OrderDeatilStatisticsDailyDao orderDeatilStatisticsDailyDao;
// 3.添加定时任务 需求:每天凌晨0点执行一次,统计昨天的数据,以此类推
@Scheduled(cron = "0 0 2 * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void dayliyStatistcsTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
log.info(" ========== dayliyStatistcsTasks start ============");
OrderDeatilDTO deatilDTO = new OrderDeatilDTO();
//获取开始时间
long startTime=System.currentTimeMillis();
// 开始时间 按 00:00:00开始 例如 2021-09-01 00:00:00
deatilDTO.setBeginDate(DateTool.getStartOfDay(DateUtils.addDays(new Date(),-4)));
// 结束时间 按 23:59:59:59 结束 2021-09-03 23:59:59:59
deatilDTO.setEndDate(DateTool.getEndOfDay(DateUtils.addDays(new Date(),-4)));
log.info(" beginDate = {} , endDate = {} ", deatilDTO.getBeginDate(),deatilDTO.getEndDate());
List orderDeatils = null;
// 手动分页 当数据量大的时候每页按500条去分页去查
PageInfo pageInfo = new PageInfo(0,500);
do {
pageInfo.setOffset((pageInfo.getPageNo() - 1) * pageInfo.getPageSize());
orderDeatils = orderDeatilDao.findOrderDeatilByDay(pageInfo,deatilDTO);
pageInfo.setPageNo(pageInfo.getPageNo() + 1);
// 处理每天消费统计
orderDeatilStatisticsDailyService.dayStatistcs(orderDeatils);
}while (CollectionUtils.isNotEmpty(orderDeatils));
// 处理当天查询数据已经存在了没, 没有就插入统计当天的日期
String today = DateUtil.formatDate(deatilDTO.getBeginDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
OrderDeatilStatisticsDaily daily = orderDeatilStatisticsDailyService.getOrderDeatilStatisticsDailyById(today);
if (daily == null){
OrderDeatilStatisticsDaily statisticsDaily = new OrderDeatilStatisticsDaily();
statisticsDaily.setCreateDate(deatilDTO.getBeginDate());
statisticsDaily.setUpdateDate(new Date());
orderDeatilStatisticsDailyDao.save(statisticsDaily);
}
//计算耗时结束时间
long endTime=System.currentTimeMillis();
log.info(" ========== dayliyStatistcsTasks end... ========== ");
log.info("============== the dayliyStatistcsTasks spend = {} ms ============== ",(endTime-startTime));
}
}
四、constant 包
(1) . 时间常量
public interface BaseConstant {
String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
String DATE_FORMAT_YYYY_MM = "yyyy-MM";
String DATE_FORMAT_YEAR_MONTH= "yyyyMM";
}
(2). 类型常量(用于策略模式声明的常量)
public class TypeConstant {
// 买菜
public static final String TYPE_BUY_VEGETABLES = "vegetables";
// 买肉
public static final String TYPE_BUY_MEAT ="meat";
// 买海鲜
public static final String TYPE_BUY_SEAFOOD = "seafood";
// 买家具
public static final String TYPE_BUY_FURNITURE = "furniture";
}
五、dao包(Mapper)
1.1. 原表数据Mapper(只需要分页查询就行)
(1). dao接口
public interface OrderDeatilDao {
/**
* @Description: 查询当天的条数据
* @param beginDate 开始时间 以 2021-11-19 00:00:00
* @param overDate 结束时间 以 2021-11-19 23:59:59
* @return OrderDeatil
*/
List findOrderDeatilList (@Param("beginDate") String beginDate, @Param("overDate") String overDate, @Param("pageInfo")PageInfo pageInfo);
/**
* 分页查询返回集合 按天以每页500调或更少条数查询 ,以此减少对数据量查询时间的压力
* @param orderDeatilDTO 参数,开始时间和结束时间
* @param pageInfo 分页
* @return
*/
List findOrderDeatilByDay(@Param("pageInfo") PageInfo pageInfo,@Param("deatilDTO")OrderDeatilDTO orderDeatilDTO);
}
(2). mapper.xml SQL文件
1.2. 原表统计到新表的Mapper
(1). 接口
public interface OrderDeatilStatisticsDailyDao {
/**
* @Description: 查询今天统计的数据是否存在
* @param today 当天时间
* @return 实例对象
*/
OrderDeatilStatisticsDaily getOrderDeatilStatisticsDailyById(@Param("today") String today);
/**
* @Description: 新增数据
* @param orderDeatilStatisticsDaily 实例对象
*/
void save(OrderDeatilStatisticsDaily orderDeatilStatisticsDaily);
/**
* @Description: 更新统计数据
* @param orderDeatilStatisticsDaily 实例对象
*/
void update(OrderDeatilStatisticsDaily orderDeatilStatisticsDaily);
}
(2) mapper.xml SQL文件
insert into order_deatil_statistics_daily(buy_vegetables, buy_meat, buy_seafood, buy_furniture, create_date, update_date, del_flag)
values (#{buyVegetables}, #{buyMeat}, #{buySeafood}, #{buyFurniture}, #{createDate}, #{updateDate}, #{delFlag})
update order_deatil_statistics_daily
buy_vegetables = #{buyVegetables} + buy_vegetables,
buy_meat = #{buyMeat} + buy_meat ,
buy_seafood = #{buySeafood} + buy_seafood,
buy_furniture = #{buyFurniture} + buy_furniture,
update_date = #{updateDate},
where id = #{id}
六、枚举类型(重点,通过查询数据对应的类型获取对应上下文的类)
public enum TypeEnum {
// 买菜
VEGETABLES_TYPE(TypeConstant.TYPE_BUY_VEGETABLES,"vegetablesStatisticsRealm"),
// 买海鲜
SEAFOOD_TYPE(TypeConstant.TYPE_BUY_SEAFOOD,"seaFoodStatisticsRealm"),
// 买肉
MEAT_TYPE(TypeConstant.TYPE_BUY_MEAT,"meatStatisticsRealm"),
// 家具
FURNITURE_TYPE(TypeConstant.TYPE_BUY_FURNITURE,"furnitureStatisticsRealm"),
;
private final String code;
private final String type;
TypeEnum(String code, String type) {
this.code = code;
this.type = type;
}
public static String getRealmByType(String code){
for (TypeEnum codeEnum: values()){
if (codeEnum.code.equals(code)){
return codeEnum.type;
}
}
return null;
}
}
七、实体类
(1). 订单详情表原表实体类
public class OrderDeatil implements Serializable {
private static final long serialVersionUID = 665390835869509423L;
/**
* 数据ID
*/
private Integer id;
/**
* 订单id
*/
private String orderNo;
/**
* 消费的用户
*/
private String payMemberId;
/**
* 消费类型
*/
private String payType;
/**
* 创建日期
*/
private Date createDate;
/**
* 消费的点数、价格
*/
private Integer actualPrice;
private Boolean delFlag;
....
(2). DTO 参数实体类
@Data
public class OrderDeatilDTO {
private Date beginDate; //创建开始时间
private Date endDate; //创建结束时间
}
(3). 从原表统计到新的实体类
public class OrderDeatilStatisticsDaily implements Serializable {
private static final long serialVersionUID = 698225930974140612L;
/**
* 数据ID
*/
private Integer id;
/**
* 买菜
*/
private Integer buyVegetables;
/**
* 买肉
*/
private Integer buyMeat;
/**
* 买海鲜
*/
private Integer buySeafood;
/**
* 买家具
*/
private Integer buyFurniture;
/**
* 创建时间
*/
private Date createDate;
/**
* 更新时间
*/
private Date updateDate;
/**
* 消费的点数、价格
*/
private Integer actualPrice;
private Boolean delFlag;
....
八、策略模式的应用
(1). 新建一个接口类,声明要实现的方法,用来处理不同统计类型的方法入口
#public interface TypeStatisticsRealm {
/**
* 处理统计接口方法
* @param statisticsDaily 存放统计后的实体类
* @param orderDeatilList 获取集合第一条数据的时间、等
* @return
*/
OrderDeatilStatisticsDaily handleOrderDetail(OrderDeatilStatisticsDaily statisticsDaily, List orderDeatilList);
}
(2). 声明抽象类实现,主要有两件事
第一件事:实现接口的方法 handleOrderDetail
第二件事:声明一个统计不同类型的方法,以便其它类去继承
public abstract class TypeStatisticsAbstractRealm implements TypeStatisticsRealm
/**
* 处理统计方法入口
* @param statisticsDaily 存放统计后的实体类
* @param orderDeatilList 获取集合第一条数据的时间、等
* @return
*/
@Override
public OrderDeatilStatisticsDaily handleOrderDetail(OrderDeatilStatisticsDaily statisticsDaily, List orderDeatilList) {
int totalPoint = 0;
if (CollectionUtils.isEmpty(orderDeatilList)){
return statisticsDaily;
}
// 获取集合按类型分组的第一条数据
OrderDeatil orderDeatil = orderDeatilList.get(0);
// 获取统计的时间
statisticsDaily.setCreateDate(orderDeatil.getCreateDate());
statisticsDaily.setUpdateDate(new Date());
// 统计不同类型的总点数
totalPoint = orderDeatilList.stream().mapToInt(OrderDeatil::getActualPrice).sum();
// 针对 不同 payType 方法 , 外遍历的时候就会利用策略模式将类注入容器里面,然后根据payType调用不同的方法
statisticsDaily = this.handleDailyStatistics(totalPoint,statisticsDaily);
return statisticsDaily;
}
/**
* 将处理不同的类型的统计存在实体类里面
* @param totalPoint 一天统计的总点数
* @param statisticsDaily 统计的实体类
* @return
*/
protected OrderDeatilStatisticsDaily handleDailyStatistics(int totalPoint,OrderDeatilStatisticsDaily statisticsDaily){
throw new AbstractMethodError();
}
}
(3) . 声明策略模式对应类型的子类去继承TypeStatisticsAbstractRealm
1.1 蔬菜类
@Component
public class VegetablesStatisticsRealm extends TypeStatisticsAbstractRealm{
@Override
protected OrderDeatilStatisticsDaily handleDailyStatistics(int totalPoint, OrderDeatilStatisticsDaily statisticsDaily) {
statisticsDaily.setBuyVegetables(totalPoint);
return statisticsDaily;
}
}
1.2 买肉类
@Component
public class MeatStatisticsRealm extends TypeStatisticsAbstractRealm{
@Override
protected OrderDeatilStatisticsDaily handleDailyStatistics(int totalPoint, OrderDeatilStatisticsDaily statisticsDaily) {
statisticsDaily.setBuyMeat(totalPoint);
return statisticsDaily;
}
}
1.3 买海鲜类
@Component
public class SeaFoodStatisticsRealm extends TypeStatisticsAbstractRealm {
@Override
protected OrderDeatilStatisticsDaily handleDailyStatistics(int totalPoint, OrderDeatilStatisticsDaily statisticsDaily) {
statisticsDaily.setBuySeafood(totalPoint);
return statisticsDaily;
}
}
1.4 买家具类
@Component
public class FurnitureStatisticsRealm extends TypeStatisticsAbstractRealm{
@Override
protected OrderDeatilStatisticsDaily handleDailyStatistics(int totalPoint, OrderDeatilStatisticsDaily statisticsDaily) {
statisticsDaily.setBuyFurniture(totalPoint);
return statisticsDaily;
}
}
九、Service
(1). 原表订单详情 Service
public interface OrderDeatilService {
/**
* @Description: 查询多条数据
* @param beginDate 开始时间
* @param overDate 结束时间
* @param pageInfo 分页
* @return
*/
List findOrderDeatilList( String beginDate,String overDate,PageInfo pageInfo);
}
(2). 将原表统计新表的方法以及处理统计service
public interface OrderDeatilStatisticsDailyService {
/**
* 通过当天查询数据
* @param today 当天统计昨天的时间 例如:2021-11-19 --》》 2021-11-18
* @return
*/
OrderDeatilStatisticsDaily getOrderDeatilStatisticsDailyById(String today);
/**
* 按天消费统计接口入口
* @param
* @param orderDeatils
*/
void dayStatistcs(List orderDeatils);
/**
* 按天处理保存接口过程
* @param value
*/
void daySave(List value);
/**
* 按天保存或更新数据
* @param statisticsDaily
*/
void saveDayStatisticsData(OrderDeatilStatisticsDaily statisticsDaily);
/**
* 保存统计方法入口
* @param statisticsDaily
* @return
*/
OrderDeatilStatisticsDaily handleStatisticsInfo(OrderDeatilStatisticsDaily statisticsDaily);
/**
* 保存或更新
* @param statisticsDaily
*/
void saveOrUpdate(OrderDeatilStatisticsDaily statisticsDaily);
}
十、Impl
(1). 原表订单详情 Service实现类
@Service("orderDeatilService")
public class OrderDeatilServiceImpl implements OrderDeatilService {
@Autowired
private OrderDeatilDao orderDeatilDao;
/**
*
* @param beginDate 开始时间
* @param overDate 结束时间
* @param pageInfo 分页
* @return
*/
@Override
public List findOrderDeatilList (String beginDate, String overDate, PageInfo pageInfo) {
List list=this.orderDeatilDao.findOrderDeatilList(beginDate,overDate,pageInfo);
return list;
}
}
(2). 将原表统计新表的方法以及处理统计实现类
(重点方法:dayStatistcs,daySave)
daySave:里面的
// 核心代码:通过查询数据的pay_type类型,然后按天和类型分组,接着通过key获取对应注入实例
String realmName = TypeEnum.getRealmByType(entry.getKey());
if (realmName != null){
// 核心代码:通过 不同的pay_type类型注入子类
TypeStatisticsRealm realm = SpringContextHolder.getBean(realmName);
// 核心代码:通过对不同的pay_type类型,使用不同的策略类型处理总点数
statisticsDaily = realm.handleOrderDetail(statisticsDaily,entry.getValue());
.......
@Service("orderDeatilStatisticsDailyService")
@Slf4j
public class OrderDeatilStatisticsDailyServiceImpl implements OrderDeatilStatisticsDailyService {
@Autowired
private OrderDeatilStatisticsDailyDao orderDeatilStatisticsDailyDao;
/**
* 查询当天数据是否存在
* @param today 统计昨天的天数
* @return
*/
@Override
public OrderDeatilStatisticsDaily getOrderDeatilStatisticsDailyById(String today) {
return this.orderDeatilStatisticsDailyDao.getOrderDeatilStatisticsDailyById(today);
}
/**
* 对原表集合按天分组
* @param orderDeatils
*/
@Override
@Transactional
public void dayStatistcs(List orderDeatils) {
log.info("=========== start dayStatistcs orderDeatils of size = {} " ,orderDeatils.size());
if (!CollectionUtils.isEmpty(orderDeatils)){
// 按天分组 重点(这块其实可以和分类一起统计,省写了一个方法)
Map> dayliyMaps = orderDeatils.stream().collect(Collectors.groupingBy(deatil -> DateUtil.dateToString(deatil.getCreateDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD)));
// 按天分组后遍历集合
for (Map.Entry> entry : dayliyMaps.entrySet()){
// 处理保存入口
this.daySave(entry.getValue());
}
log.info("=========== end dayStatistcs =========== ");
}
}
/**
* 对原表集合按类型分组后对不同类型进行处理后的总点数set上统计实体类后去保存
* @param orderDeatils
*/
@Override
@Transactional
public void daySave(List orderDeatils) {
// 创建统计类型的实体类
OrderDeatilStatisticsDaily statisticsDaily = new OrderDeatilStatisticsDaily();
// 1. 按消费类型处理所有类型的统计
Map> typeListMap = orderDeatils.stream().collect(Collectors.groupingBy(OrderDeatil :: getPayType));
for (Map.Entry> entry : typeListMap.entrySet()) {
// 通过key获取对应注入实例
String realmName = TypeEnum.getRealmByType(entry.getKey());
if (realmName != null){
TypeStatisticsRealm realm = SpringContextHolder.getBean(realmName);
// 处理类型的统计值
statisticsDaily = realm.handleOrderDetail(statisticsDaily,entry.getValue());
List values = entry.getValue().stream().map(s -> "id:" + s.getId() + ",point:" + s.getPayType()).collect(Collectors.toList());
log.info("statisticsDaily statisticsDailyCodeEnum:[{}], values[{}]", entry.getKey(), values);
}
}
this.saveDayStatisticsData(statisticsDaily);
}
/**
* 保存统计数据,中间可以处理其它数据的方法
* @param statisticsDaily
*/
@Override
@Transactional
public void saveDayStatisticsData(OrderDeatilStatisticsDaily statisticsDaily) {
// 处理保存
this.handleStatisticsInfo(statisticsDaily);
}
/**
* 处理保存或更新过程
* @param statisticsDaily
* @return
*/
@Override
@Transactional
public OrderDeatilStatisticsDaily handleStatisticsInfo(OrderDeatilStatisticsDaily statisticsDaily) {
// 保存或更新
this.saveOrUpdate(statisticsDaily);
return statisticsDaily;
}
/**
* 查询当天存在就保存,否则更新
* @param statisticsDaily
*/
@Override
@Transactional
public void saveOrUpdate(OrderDeatilStatisticsDaily statisticsDaily) {
// 处理当天时间为 yy-mm-dd
String today = DateUtil.formatDate(statisticsDaily.getCreateDate(),BaseConstant.DATE_FORMAT_YYYY_MM_DD);
// 1. 判断统计昨天有没有数据,没有的话就创建一条
OrderDeatilStatisticsDaily daily = orderDeatilStatisticsDailyDao.getOrderDeatilStatisticsDailyById(today);
if (daily == null){
statisticsDaily.setUpdateDate(new Date());
statisticsDaily.setDelFlag(true);
orderDeatilStatisticsDailyDao.save(statisticsDaily);
} else {
// 否则就更新
statisticsDaily.setId(daily.getId());
statisticsDaily.setUpdateDate(new Date());
orderDeatilStatisticsDailyDao.update(statisticsDaily);
}
}
}
十一、Utils 工具类
(1). 时间类
public class DateTool {
/**
* 获得某天最小时间 2019-7-17 00:00:00
* @param date
* @return
*/
public static Date getStartOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 获得某天最大时间 2019-7-17 23:59:59
* @param date
* @return
*/
public static Date getEndOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
}
}
####(2).
public class DateUtil {
public static String formatDate(Date date, Object... pattern) {
String formatDate = null;
if (pattern != null && pattern.length > 0) {
formatDate = DateFormatUtils.format(date, pattern[0].toString());
} else {
formatDate = DateFormatUtils.format(date, "yyyy-MM-dd");
}
return formatDate;
}
public static Date addDays(Date date, int amount) {
return add(date, 5, amount);
}
private static Date add(Date date, int calendarField, int amount) {
if (date == null) {
throw new IllegalArgumentException("The date must not be null");
} else {
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(calendarField, amount);
return c.getTime();
}
}
// 按照格式将日期转化成字符串
public static String dateToString(Date d, String format) {
SimpleDateFormat formatter = new SimpleDateFormat(format);
if (d == null) {
return "";
} else {
return formatter.format(d);
}
}
}
十二:分页类
public class PageInfo implements Serializable {
private int pageNo; //页码
private int offset; // 开始条数
private int pageSize; //页大小
private String orderBy; //排序
private int totalItem; //总条数
public PageInfo () {
}
public PageInfo(int pageNo, int pageSize) {
super();
if (pageNo < 1) {
pageNo = 1;
}
this.pageNo = pageNo;
this.pageSize = pageSize;
}
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
if (pageNo < 1) {
pageNo = 1;
}
this.pageNo = pageNo;
}
@JsonIgnore
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
@JsonIgnore
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public int getTotalItem() {
return totalItem;
}
public void setTotalItem(int totalItem) {
this.totalItem = totalItem;
}
}
十三. 获取getBean上下文工具
public class SpringContextHolder implements ApplicationContextAware , DisposableBean {
private static ApplicationContext applicationContext = null;
private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
public SpringContextHolder() {
}
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
public static T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
public static T getBean(Class requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
public static void clearHolder() {
if (logger.isDebugEnabled()) {
logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
@Override
public void destroy() throws Exception {
clearHolder();
}
private static void assertContextInjected() {
Validate.validState(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.", new Object[0]);
}
}
结语
主要目的事为了解决某一张表数据量大(当超过几千万W级别这种方案可能不太适合)的时候做统计比较慢的方案,当中使用了一张新表去做原表统计后按天的总额,并用策略模式对不同类型进行处理(优点:遵守开闭原则,比if else更加有效,不需要在原有的类做修改,只需要增加类就行,缺点:类型越多,类也就会多,增加类的臃肿程度)。
最后:下篇会为新的业务要求新增一个导出功能,只能使用异步的方式导出(例如Rabbimt这种)