SpringBoot集成JRES-RPC 实现T2服务

项目背景

  1. 需要使用Java对接第三方平台,提供给C程序调用
  2. 保持项目架构简单、容易部署

过程

  1. 新建一个SpringBoot项目
  2. 参考JRES Server框架说明,单独引入RPC组件,主要是引入以下依赖:

    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
    <dependency>
    <groupId>com.hundsun.jresplus</groupId>
    <artifactId>jresplus-rpc-api</artifactId>
    <version>1.2.2</version>
    </dependency>
    <dependency>
    <groupId>com.hundsun.jresplus</groupId>
    <artifactId>jresplus-rpc-core</artifactId>
    <version>1.2.2</version>
    </dependency>
    <dependency>
    <groupId>com.hundsun.jresplus</groupId>
    <artifactId>jresplus-rpc-spi-t2def</artifactId>
    <version>1.2.2</version>
    </dependency>
    <dependency>
    <groupId>com.hundsun.jresplus</groupId>
    <artifactId>jresplus-rpc-spi-spring4</artifactId>
    <version>1.2.2</version>
    </dependency>
    <dependency>
    <groupId>com.hundsun.jresplus</groupId>
    <artifactId>jresplus-common-core</artifactId>
    <version>1.2.0</version>
    </dependency>
  3. 在启动类中引入JRES-RPC的Bean配置并设置配置文件路径
    iWrDl6.md.png

  4. 配置好server.properties和ares-app-config.xml文件,并放到resources目录下
  5. 开发remoting接口,并使用HSAdmin工具测试 (可以咨询研发中心客服苏响)

遇到的一些问题

  1. 引入SpringBoot的热部署工具spring-boot-devtools之后,参数为JavaBean的T2接口报argument type miss match。
    调试后发现,热部署工具使用RestartedClassLoader加载类,而T2接口的参数Bean是由其它的类加载器加载,所以虽然是同一个类型,但是Class对象不一致,反射调用method报错。
    移除spring-boot-devtools后成功解决。
  2. 项目打成Jar包运行之后 发现T2Server没有启动,也没有报错。
    几经排查,对比Application类Main启动的方式下的日志,怀疑XML配置的JavaBean CepContextLoader没有加载,因此把配置文件从jresplus-rpc-spi-spring4中拿出来放到项目路径下,将其中的CepContextLoader改为@Bean加载。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @SpringBootApplication
    //注释掉jresplus-cep-beans.xml里的CepContextLoader和SpringPluginFramework配置
    @ImportResource({"classpath:config/jresplus-cep-beans.xml","classpath:config/remoting-main-beans.xml"})
    @Slf4j
    public class CopApplication {

    public static void main(String[] args) {
    System.setProperty("JresConfigLocation","classpath:server.properties");
    SpringApplication.run(CopApplication.class, args);
    }

    @Bean("jres.framework")
    public SpringPluginFramework springPluginFramework(){
    return new SpringPluginFramework();
    }

    @Bean
    CepContextLoader CepContextLoader(SpringPluginFramework springPluginFramework){
    CepContextLoader cepContextLoader = new CepContextLoader();
    cepContextLoader.setPropPath("classpath*:ares-app-config.xml");
    cepContextLoader.setFramework(springPluginFramework);
    return cepContextLoader;
    }
    }

重新打包后,测试可用。

JAVA培训大纲

入门

  • 为什么要用java
    1. 平台无关性,方便跨平台部署
    2. 面向对象(封装、继承、多态)
    3. 强大的原生库 (屏蔽底层细节)
    4. 丰富的第三方库
    5. 各种成熟的解决方案
  • 开发准备
    1. JDK (源码、工具、JRE)
    2. Maven (管理第三方依赖,编译打包等)
    3. IDE (辅助编码、调试运行等)
    4. Web容器(tomcat/jetty等)
  • 要学哪些东西 (了解、会用、理解)
    1. 语法 (enum、注解)
    2. 编码规范
      1. 命名
      2. 日志
      3. 异常处理
      4. 注释
    3. 常用的原生类(java.lang、java.util)
    4. 常用的框架
      1. Spring&SpringMVC
      2. MyBatis

进阶

  • 并发编程
  • 抽象建模(服务、接口设计等)
  • 自顶向下、逐层分解
  • JVM内存模型
  • 常见问题排查

踩坑记5-Mybatis合集

从刚参加工作开始就接触了Mybatis,但是大部分时候只是照猫画虎,一直没有系统的学习过,遇到些问题就很容易卡住,搜不到解决方法,只能参照错误逐行排查来定位问题,效率十分低下。
反思之后,决定从头学习一下,顺便整理下踩过的坑,分析下原因。
官网链接

MyBatis是什么

它是一款持久层框架,支持自定义SQL、存储过程和结果集映射。通过XML或者注解来实现ORM映射。

依赖

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

与Spring框架的集成

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>x.x.x</version>
</dependency>

框架配置

1. 数据源(必须)、SqlSessionFactory
2. Settings 缓存、代理、作用域等
3. typeAliases POJO别名
4. typeHandlers 可以自定义类型映射,比如Enum映射为Name or Order
5. environments 
    - 不同环境不同设置(开发、测试、生产等)
    - 多数据源对应多个SqlSessionFactory
6. 插件 实现Interceptor接口,拦截下列方法
    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    ParameterHandler (getParameterObject, setParameters)
    ResultSetHandler (handleResultSets, handleOutputParameters)
    StatementHandler (prepare, parameterize, batch, update, query)
7.  Mappers XML映射文件

Mapper语法

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
    • 自动映射以及驼峰映射mapUnderscoreToCamelCase=true\
    • 集合嵌套association、collection等
    • 鉴别器discriminator case判断
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句
    PS: 缓存使用的时候可以参考聊聊MyBatis缓存机制
    不过建议使用Spring+Redis实现,更靠谱一些

动态 SQL

  • if
  • choose/when/otherwise
  • bind
  • trim, where, set
  • foreach

JAVA API

  • 注解
  • SqlSession
  • SqlBuilder

问题集合

  1. insert操作报 无效的列:111
    • 原因: 插入空值到非空字段

Java根据模板生成PDF文件

需求场景

根据业务自定义模板,按照模板导出相应格式数据,包括文字、图片等

解决方案

1. Adobe Acrobаt编辑PDF模板
2. itextpdf5 填入业务参数

依赖

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

参考资料

官网例子

过程

制作PDF模板

这个过程,没啥技术含量,只讲下经验:

  1. 先用Word编辑模板,然后导出成PDF,比直接编辑PDF方便
  2. Acrobаt打开PDF,准备表单,需要注意
    • 字体一定要选带中文的,比如宋体、雅黑等
    • 属性一定选可见
    • 表单域名 一定要和业务参数名对应

根据模板生成文件

用了PdfReader、PdfStamper和AcroFields,实现文本域和图像域内容的写入。
params参数集的key必须与表单域名一一对应,其他的直接看代码:

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
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PushbuttonField;

public class PdfUtil {

/**
*
* @param src PDF模板路径
* @param dest 生成文件路径
* @param params 参数集。若为图片,key中需包含image,value为图片地址
* @throws DocumentException
* @throws IOException
*/
public static void generatePDF(String src, String dest, Map<String,String> params) throws DocumentException, IOException {

File file = new File(dest);
file.getParentFile().mkdirs();
PdfReader reader =null;
PdfStamper stamper = null;
try{
reader= new PdfReader(src);
stamper = new PdfStamper(reader, new FileOutputStream(dest));
AcroFields form = stamper.getAcroFields();
for (String key :form.getFields().keySet()){
if (params.get(key)!=null){
//根据表单域名区分类型
if (!key.startsWith("Image")){
//设置文本域参数值
form.setField(key,params.get(key));
}else{
//将图片写入指定的field
Image image = Image.getInstance(params.get(key));
PushbuttonField pb = form.getNewPushbuttonFromField(key);
pb.setImage(image);
form.replacePushbuttonField(key, pb.getField());
}
}
}
stamper.setFormFlattening(true);
}finally {
try{
if (stamper!=null){
stamper.close();
}
}catch (Exception e){
}
try{
if (reader!=null){
reader.close();
}
}catch (Exception e){
}
}
}
}

结尾

  1. IText类库的功能非常强大,支持各种控件的操作,包括表单、表格、字体、图片等,还具有文件合并、转格式、加密、签名等功能。
    可惜的是中文的资料不多,如果需要深入使用,建议直接去官网上看API文档和Examples示例。
  2. 另外,由于是国外的产品,对中文的处理有一些坑在官网上并未提及,需要想办法解决,比如前面依赖的itext-asian包和模板编辑时字体的选择 就属此类。

归档历史表后Mysql重启自增主键重置问题

问题场景

COP日初始化时,会将当前表数据归档到历史表,然后把数据从当前表删除。
由于MySql InnoDB数据库自增列auto_increment的最大值保存在内存中,启动后auto_increment会重置为当前表的最大值+1。
如果此时重启数据库,由于当前表无数据,重启后当前表新增的数据主键就变为1。
再次日初始化时,由于历史表中已存在主键为1的数据,就会主键冲突。

解决方案

1. 升级Mysql到8.0.0版本 (参考官网bug issue,未验证) 
2. 为当前表添加触发器,插入数据时,取当前表和历史表中较大的auto_increment作为当前表的auto_increment。
    以COP为例,当前表为cop_tliquidateins,历史表为cop_thisliquidateins,主键为liquidate_ins_id,按以下方式创建触发器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delimiter $$
drop trigger if exists trig_autoinc_liquidateins;
CREATE TRIGGER trig_autoinc_liquidateins BEFORE INSERT ON cop_tliquidateins
FOR EACH ROW
BEGIN
declare auto_incr1 BIGINT;
declare auto_incr2 BIGINT;
IF (NEW.liquidate_ins_id=0) THEN
SELECT AUTO_INCREMENT INTO auto_incr1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = SCHEMA() and UPPER(TABLE_NAME) = UPPER('cop_tliquidateins');
SELECT AUTO_INCREMENT INTO auto_incr2 FROM information_schema.TABLES WHERE TABLE_SCHEMA = SCHEMA() and UPPER(TABLE_NAME) = UPPER('cop_thisliquidateins');
IF (auto_incr2 > auto_incr1) THEN
SET NEW.liquidate_ins_id = last_insert_id(auto_incr2);
END IF;
END IF;
END;
delimiter ;

参考资料

Mysql bugs

触发器方案