利用Velocity在线编写后台接口

2022-04-20 00:00:00 数据库 执行 连接 模板 数据源

开发中,有时候需要用到不同的数据库,一般是用动态数据源,然后书写接口,但是在不同版本中,有时候有些接口有不需要,而且如果接口写在java代码中,不是很好维护,今天听到一个需求,利用Velocity + 动态数据源 jdbcTemplate,去写动态接口,二话不说我们来撸串串。

1 Velocity
Velocity 是一种基于Java的模板引擎,其实我们有时候也与这儿打过交道,用的多就是代码生成。我们利用它的模板语言之后可以将我们传输的数据写进sql语句,有点类似于Mybaits 写sql,然后利用#{}代替参数。


2 JdbcTemplate
JdbcTemplate 我们可以利用这个执行我们所需要的sql,并提供事务控制,直接获取数据,这里不采用java的jdbc,太繁琐了。

准备工作做完了,我们可以开始动手撸我们的代码了。
步:
我们需要获取不同的数据源,这里我们投个懒,我们
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!-- 配置连接池 -->
<bean id="dataSource_xxxxxx" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName">
<value>${ica.jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${ica.jdbc.url}</value>
</property>
<property name="username">
<value>${ica.jdbc.username}</value>
</property>
<property name="password">
<value>${ica.jdbc.password}</value>
</property>
<!-- 连接池大使用连接数 -->
<property name="maxActive">
<value>10</value>
</property>
<!-- 初始化连接大小 -->
<property name="initialSize">
<value>3</value>
</property>
<!-- 获取连接大等待时间 -->
<property name="maxWait">
<value>60000</value>
</property>
<!-- 连接池小空闲 -->
<property name="minIdle">
<value>3</value>
</property>
<!-- 自动清除无用连接 -->
<property name="removeAbandoned">
<value>true</value>
</property>
<!-- 清除无用连接的等待时间 -->
<property name="removeAbandonedTimeout">
<value>1800</value>
</property>
<!-- 连接属性 -->
<property name="connectionProperties">
<value>clientEncoding=UTF-8</value>
</property>
<property name="testOnBorrow">
<value>false</value>
</property>
<property name="testWhileIdle">
<value>true</value>
</property>
<property name="validationQuery">
<value>${ica.jdbc.validationQuery}</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>30000</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>60000</value>
</property>
<!-- 连接属性 -->
<property name="testOnReturn">
<value>false</value>
</property>
<!-- 连接属性 -->
<property name="poolPreparedStatements">
<value>true</value>
</property>
<!-- 连接属性 -->
<property name="maxPoolPreparedStatementPerConnectionSize">
<value>100</value>
</property>
</bean>

<bean id="jdbcTemplate_xxxxxx" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource_xxxxxx"></property>
</bean>

<!-- 定义事务管理器 由于后加载采用编程事务-->
<bean id="transactionManager_xxxxxx"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource_xxxxxx" />
</bean>


</beans>

我们直接写了一个xml文件,然后当我们spring初始化的时候,把xxxxxx的值给替换成一个的名字,然后将url,数据库账号密码给替换掉,这样我们就可以直接生成一个javabean,这样当我们取不同的数据源时,只要根据名字拿到不同的jdbcTemplate,transactionManager,这样我们就可以开启我们的sql执行和事务控制了。


第二步:bean的生成

我们可以利用spring 的上下文初始化完成时,去生成bean,一般我们都有一个工具文件,专门用来获取bean的,这样我们可以利用BeanDefinitionRegistry,ApplicationContext

来初始化我们的数据源。

public static synchronized void initDataSource(){
try {
System.out.println("=======================initDataSource==============================");
IccnIcaDbService sysDbService = (IccnIcaDbService) applicationContext.getBean("iccnIcaDbService");
List<IccnIcaDb> sysDbs = sysDbService.findList(new IccnIcaDb());
Resource resource = new ClassPathResource("/applicationContext-xxxxxx.xml", IcaSpringContextHolder.class);
String xml = IOUtils.toString(resource.getInputStream(),"UTF-8");
for (IccnIcaDb sysDb : sysDbs) {
String jdbcDriverclassname = sysDb.getJdbcDriverclassname();
String jdbcUrl = sysDb.getJdbcUrl();
String jdbcUsername = sysDb.getJdbcUsername();
String jdbcPassword = sysDb.getJdbcPassword();
String beanName = sysDb.getMd5BeanName();
if("0".equals(sysDb.getStatus().trim())){
continue;//改db不启用
}
if (DbConnTest.connTestByDriver(jdbcDriverclassname, jdbcUrl, jdbcUsername, jdbcPassword)) {//测试该连接是否正确,成功才生成bean
if ( ! registry.isBeanNameInUse("transactionManager_"+beanName)) {
String temp = xml.replaceAll("xxxxxx", beanName);
temp = temp.replace("${ica.jdbc.driverClassName}", jdbcDriverclassname);
temp = temp.replace("${ica.jdbc.url}", jdbcUrl);
temp = temp.replace("${ica.jdbc.username}", jdbcUsername);
temp = temp.replace("${ica.jdbc.password}", jdbcPassword);
if (jdbcDriverclassname.toUpperCase().indexOf("ORACLE") != -1) {
temp = temp.replace("${ica.jdbc.validationQuery}", "SELECT 1 FROM DUAL");
} else {
temp = temp.replace("${ica.jdbc.validationQuery}", "SELECT 1");
}
//logger.debug("applicationContext-xxxxxx.xml:{}", temp);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.setValidating(false);
reader.loadBeanDefinitions(new InputSource(new StringReader(temp)));
}
}else{
logger.error("连接失败:"+jdbcUrl);
}
}
} catch (IOException e) {
logger.error("postProcessBeanFactory", e);
}
}
数据库,连接测试的方法在这,为什么不用DriverManager,原因我上一篇博客有讲

public static boolean connTestByDriver(String driver, String dbUrl, String user, String password) {
// 创建数据库连接对象
Connection connnection = null;

boolean result = false;
// 加载驱动程序
try {
Driver driverClass = (Driver) Class.forName(driver).newInstance();
// 获取连接
Properties info = new Properties();
if(!StringUtils.isEmpty(user)){
info.put("user", user);
}
if(!StringUtils.isEmpty(password)){
info.put("password", password);
}
connnection = driverClass.connect(dbUrl, info);
//connnection = DriverManager.getConnection(dbUrl, user, password);
if(connnection!=null){
result = true;
}else{
throw new Exception("数据库驱动不匹配,请修改为正确的数据库驱动");
}
}catch (Exception e) {
e.printStackTrace();
} finally{
if(connnection != null){
try {
connnection.close();
} catch (SQLException e) {
}
}
}
return result;
}


我们将数据源的信息写入到数据库中,然后从数据库取出数据,生成动态数据源,
private Long id; //主键
private String dbId; //数据库链接id标识
private String dbName; //数据库链接名称
private String dbType; //数据库类型
private String dbVersion; //数据库版本
private String jdbcDriverclassname; //数据库驱动
private String jdbcUrl; //数据库链接url
private String jdbcUsername; //数据库用户名
private String jdbcPassword; //数据库密码
private String description; //描述
private Integer status; //状态 0:关闭 1:启用

这是实体类,包含了我们生成动态数据源的一些基本信息,这里,我们替换成的bean名字,我们采用数据库url 账号密码,生成
public String getMd5BeanName() {
return MD5Tools.MD5(jdbcDriverclassname+":"+jdbcUrl+":"+jdbcUsername+":"+jdbcPassword);
}

动态数据源工作完成了,我们就可以开始利用velocity,写sql了,如果对velocity不懂的同学,可以去百度搜索下,模板语言的语法。
private Long id; //主键
private String dbId; //数据库链接id
private String serviceCode; //服务编码
private String serviceName; //服务名称
private String serviceParams; //服务参数
private String dbSql; //数据库sql
private String responeTemplate; //返回模板
private String description; //描述
private Integer status; //状态 0:关闭 1:启用

这是我们的服务类,服务编码,是用来调用接口的,如我们controller层的地址是 /service/{serviceCode},这样我们就可以知道具体调用的是哪个服务了,然后

这就是方法的入口,服务参数代表外界传过来的参数,然后返回模板,是代表我们返回的形式,

所以我们将会执行两次模板解析,一次将服务参数解析进对应的sql语句,然后执行完sql语句后,讲得到的数据模板解析进返回模板

我在这里展示一个简单的例子

服务参数

{
"std_data": {
"parameter": {
"ent": "99",
"site": "DSCNJ",
"receivedQty": "101.787",
"unitNo": "KG"
}
}
}
sql
#set ($bodyparams = $bodyMap.std_data.parameter)
#set ($ent = $bodyparams.ent)
#set ($receivedQty= $bodyparams.receivedQty)
#set ($unitNo = $bodyparams.unitNo)


SELECT round(nvl('$receivedQty', 0), ooca002) as "convertQty",
DECODE('$receivedQty',round(nvl('$receivedQty', 0), ooca002),0,1) as "check",
ooca002 as "decimalPlace"
FROM ooca_t
WHERE oocaent = '$ent'
AND ooca001 = '$unitNo'
返回模板

{
"std_data": {
"execution": {
"code": "$execution.code",
"sqlcode": "$execution.sqlcode",
"description": "$execution.description"
},
"parameter": {
"result": [
{
"listdata": $JSON.toJSONString($sqlData[0])
}
]
}
}
}
而且我们需要指定数据库连接,我们将会把sql解析成

SELECT round(nvl('101.787', 0), ooca002) as "convertQty",
DECODE('101.787',round(nvl('101.787', 0), ooca002),0,1) as "check",
ooca002 as "decimalPlace"
FROM ooca_t
WHERE oocaent = '99'
AND ooca001 = 'KG'

然后我们来看看我们的参数解析
// 1.模板上下文环境
VelocityEngine ve = new VelocityEngine();
VelocityContext context = new VelocityContext();
context.put("StringUtils", StringUtils.class);
context.put("DateUtils", DateUtils.class);
context.put("JSON", JSON.class);
context.put("String", String.class);
context.put("Integer", Integer.class);
context.put("VEUtils", VEUtils.class);
context.put("headerMap", headerMap);
context.put("bodyMap", bodyMap);
Map<String, String> execution = new HashMap<String, String>();
execution.put("code", "0");
execution.put("sqlcode", "0");
execution.put("description", "执行成功!");
context.put("execution", execution);
String isSuccess = "1";
// 2.解析SQL,并执行
try {
dbSqlWriter = new StringWriter();
ve.evaluate(context, dbSqlWriter, serviceCode, sqlTemplate);
String[] dbsqls = dbSqlWriter.toString().split(";(\\r\\n|\\n)");
long sqlStartTime = System.currentTimeMillis();
executeSql(sysDbBean,dbsqls, context);
logger.info("sql执行时间:"+(System.currentTimeMillis()-sqlStartTime));
//sysServiceService.executeSql(sysDbBean,dbsqls, context, serviceCode, ve);
} catch (Exception e) {
logger.error(serviceCode + "执行错误", e);
execution.put("code", "-1");
execution.put("sqlcode", e.getCause()!=null?e.getCause().getMessage():e.getMessage()==null?e.toString():e.getMessage());
execution.put("description", e.getMessage()==null?e.toString():e.getMessage());
context.put("status", "-1");
context.put("totolCount", 0);
isSuccess="0";
}
// 3.解析 响应并执行
String responeTemplat = bean.getResponeTemplate();
StringWriter responeWriter = new StringWriter();
ve.evaluate(context, responeWriter, serviceCode, responeTemplat);
respone = responeWriter.toString();
logger.info("{} 响应:{}", logTag, respone);
String result = respone;
logger.info("总耗时:"+(System.currentTimeMillis()-oldTime));
return result;

其中executeSql房啊具体如下
public void executeSql(IccnIcaDb sysDbBean, String[] dbsqls, VelocityContext context) throws Exception {
//获取事务管理
DataSourceTransactionManager transactionManager_erp = (DataSourceTransactionManager) IcaSpringContextHolder.getBean("transactionManager_"+sysDbBean.getMd5BeanName());
//采用默认的事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
//事务状态
TransactionStatus status = transactionManager_erp.getTransaction(def);
try {
JdbcTemplate jdbcTemplate_erp = IcaSpringContextHolder.getJdbcTemplate("jdbcTemplate_" + sysDbBean.getMd5BeanName());
Object[] sqlData = new Object[dbsqls.length];
int totolCount = 0;
for (int i = 0; i < dbsqls.length; i++) {
String sql = dbsqls[i].trim();
if (StringUtils.isNotBlank(sql)) {
logger.info(sql);
if (sql.toUpperCase().indexOf("MERGE") != -1 || sql.toUpperCase().indexOf("INSERT") != -1 || sql.toUpperCase().indexOf("UPDATE") != -1 || sql.toUpperCase().indexOf("DELETE") != -1) {
jdbcTemplate_erp.execute(sql);
totolCount++;
} else {
List<Map<String, Object>> data = jdbcTemplate_erp.queryForList(sql);
sqlData[i] = data;
}
}
}
context.put("totolCount", totolCount);
context.put("status", "0");
context.put("sqlData", sqlData);
} catch (Exception e) {
transactionManager_erp.rollback(status); // 也可以執行status.setRollbackOnly();
throw e;
}
transactionManager_erp.commit(status);
}


注意,需要区分是查询语句还是其他语句,并获取所有的查询结果放入object数组,注意,我们开始有放入json的class,这样在我们执行的时候是可以利用Json的方法的。

到这里,我们的动态接口就完成了,这样的好处有很多,:可以在线上修改接口。第二:我们可以记录同意的日志,执行的sql语句等等。第三:我们可以给不同的用户对于同一个接口有不同的处理,好处多多。
————————————————
版权声明:本文为CSDN博主「叶半仙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32722239/article/details/78730157

相关文章