anyline 数据库操作
AnylineService(配合AnylineDao)提供了常用的数据库操作接口,其中insert,update,delete,execute比较简单也容易理解而select操作相对灵活的多,灵活性主要体现在其参数ConfigStore的构造方式上,通过ConfigStore可以实现非常复杂的查询操作
public DataSet selects(String src, ConfigStore configs, String ... conditions);
在实际开发过程中,通常是用BaseController继承tAnylineControllerAnylineController中已经注入AnylineService serive,并重载了大量config函数用来自动构造ConfigStore
更详细的操作:AnylineService 与config()
AnylineService
AnylineService用来完成大部分的数据库操作为保持版本兼容query与select功能完全一致
/**
* 按条件查询
* @param src 数据源(表或自定义SQL或SELECT语句) src 数据源(表或自定义SQL或SELECT语句)
* @param configs 封装来自于http的查询条件 configs 封装来自于http的查询条件
* @param conditions 固定查询条件 conditions 固定查询条件
* 原生SQL(AND GROUP ORDER)
* {原生}
* [+]CD:1
* [+]CD:
* [+]CD:null
* [+]CD:NULL
*
* @return return
*/
public DataSet querys(String src, ConfigStore configs, String ... conditions);
public DataSet querys(String src, String ... conditions);
public DataSet querys(String src, int fr, int to, String ... conditions);
public DataRow query(String src, ConfigStore configs, String ... conditions);
public DataRow query(String src, String ... conditions);
//实现与query相同的功能
public DataSet selects(String src, ConfigStore configs, String ... conditions);
public DataSet selects(String src, String ... conditions);
public DataSet selects(String src, int fr, int to, String ... conditions);
public DataRow select(String src, ConfigStore configs, String ... conditions);
public DataRow select(String src, String ... conditions);
/**
* 如果二级缓存开启 会从二级缓存中提取数据
* @param cache 对应ehcache缓存配置文件 中的cache.name
* @param src src
* @param configs configs
* @param conditions conditions
* @return return
*/
public DataSet caches(String cache, String src, ConfigStore configs, String ... conditions);
public DataSet caches(String cache, String src, String ... conditions);
public DataSet caches(String cache, String src, int fr, int to, String ... conditions);
/**
* 只用一级缓存 忽略二级缓存
* @param cache cache
* @param src src
* @param configs configs
* @param conditions conditions
* @return return
*/
public DataSet cacheL1(String cache, String src, ConfigStore configs, String ... conditions);
public DataSet cacheL1(String cache, String src, String ... conditions);
public DataSet cacheL1(String cache, String src, int fr, int to, String ... conditions);
public DataRow next(DataRow row, String column, SQL.ORDER_TYPE order, ConfigStore configs, String ... conditions);
public DataRow next(DataRow row, String column, SQL.ORDER_TYPE order, String ... conditions);
public DataRow next(DataRow row, SQL.ORDER_TYPE order, String ... conditions);
public DataRow next(DataRow row, ConfigStore configs, String ... conditions);
public DataRow next(DataRow row, String ... conditions);
public DataRow prev(DataRow row, String column, SQL.ORDER_TYPE order, ConfigStore configs, String ... conditions);
public DataRow prev(DataRow row, String column, SQL.ORDER_TYPE order, String ... conditions);
public DataRow prev(DataRow row, SQL.ORDER_TYPE order, String ... conditions);
public DataRow prev(DataRow row, ConfigStore configs, String ... conditions);
public DataRow prev(DataRow row, String ... conditions);
public DataRow cache(String cache, String src, ConfigStore configs, String ... conditions);
public DataRow cache(String cache, String src, String ... conditions);
/**
* 删除缓存 参数保持与查询参数完全一致
* @param channel channel
* @param src src
* @param configs configs
* @param conditions conditions
* @return return
*/
public boolean removeCache(String channel, String src, ConfigStore configs, String ... conditions);
public boolean removeCache(String channel, String src, String ... conditions);
public boolean removeCache(String channel, String src, int fr, int to, String ... conditions);
/**
* 清空缓存
* @param channel channel
* @return return
*/
public boolean clearCache(String channel);
/**
* 是否存在
* @param src src
* @param configs configs
* @param conditions conditions
* @return return
*/
public boolean exists(String src, ConfigStore configs, String ... conditions);
public boolean exists(String src, String ... conditions);
public boolean exists(String src, DataRow row);
public boolean exists(DataRow row);
public int count(String src, ConfigStore configs, String ... conditions);
public int count(String src, String ... conditions);
/**
* 更新记录
* @param columns 需要更新的列
* @param dest 表
* @param data data
* @return return
*/
public int update(String dest, Object data, String ... columns);
public int update(Object data, String ... columns);
public int update(String dest, ConfigStore configs, String ... conditions);
public int update(boolean sync, String dest, Object data, String ... columns);
public int update(boolean sync, Object data, String ... columns);
/**
* 保存(insert|update)
* @param data data
* @param checkPriamry checkPriamry
* @param columns columns
* @param dest 表
* @return return
*/
public int save(String dest, Object data, boolean checkPriamry, String ... columns);
public int save(Object data, boolean checkPriamry, String ... columns);
public int save(Object data, String ... columns);
public int save(String dest, Object data, String ... columns);
//
public int save(boolean sync, String dest, Object data, boolean checkPriamry, String ... columns);
public int save(boolean sync, Object data, boolean checkPriamry, String ... columns);
public int save(boolean sync, Object data, String ... columns);
public int save(boolean sync, String dest, Object data, String ... columns);
public int insert(String dest, Object data, boolean checkPriamry, String ... columns);
public int insert(Object data, boolean checkPriamry, String ... columns);
public int insert(Object data, String ... columns);
public int insert(String dest, Object data, String ... columns);
/**
* 异步插入
* @param dest dest
* @param data data
* @param checkPriamry checkPriamry
* @param columns columns
* @return return
*/
public int batchInsert(String dest, Object data, boolean checkPriamry, String ... columns);
public int batchInsert(Object data, boolean checkPriamry, String ... columns);
public int batchInsert(Object data, String ... columns);
public int batchInsert(String dest, Object data, String ... columns);
/**
* save insert区别
* 操作单个对象时没有区别
* 在操作集合时区别:
* save会循环操作数据库每次都会判断insert|update
* save 集合中的数据可以是不同的表不同的结构
* insert 集合中的数据必须保存到相同的表,结构必须相同
* insert 将一次性插入多条数据整个过程有可能只操作一次数据库 并 不考虑update情况 对于大批量数据来说 性能是主要优势
*
*/
/**
* 执行
* @param src src
* @param configs configs
* @param conditions conditions
* @return return
*/
public int execute(String src, ConfigStore configs, String ... conditions);
public int execute(String src, String ... conditions);
/**
* 执行存储过程
* @param procedure procedure
* @param inputs inputs
* @return return
*/
public boolean executeProcedure(String procedure, String... inputs);
public boolean executeProcedure(Procedure procedure);
/**
* 根据存储过程查询
* @param procedure procedure
* @param inputs inputs
* @return return
*/
public DataSet queryProcedure(String procedure, String ... inputs);
public DataSet query(Procedure procedure);
public DataSet selectProcedure(String procedure, String ... inputs);
public DataSet select(Procedure procedure);
/**
* 删除 根据主键删除
* @param dest dest
* @param data data
* @return return
*/
public int delete(String dest, Object data);
public int delete(Object data);
public int delete(String table, String key, Collection<Object> values);
public int delete(String table, String key, String ... values);
关于更新部分属性(列)、忽略部分属性(列)
添加到DataRow中但不需要参与更新(插入)
row.put("-NAME", "ZH");
添加了空值, 默认情况下不参与更新(插入) 如果需要强制参与更新(插入)
row.put("+NAME",null);
如果这样指定了更新(插入)列,则只会更新(插入)指定的列,其他列都不会参与更新(插入)
service.save(row,"NAME");
强制更新(插入)NAME,忽略CODE,其他列不受影响,按默认情况处理
service.save(row,"+NAME","-CODE");
插入所有列,更新所有值发生过变化的列
service.save(row);
关于自动检测表结构
在执行数据库操作时,许多参数是以String形式获取到的,无法参数识别数据类型,如url中的参数
还有一些数据类型在Java中没有对应关系,如xml/josn/几何图形等
而有些数据库在执行SQL时会执行强类型检测。
不像我们平时用的MySQL在执行时会进行隐式转换,无论什么类型只要能转换成功就可以执行。
而PostgreSQL则要求jdbc参数与表结构对应,如果在varchar列中执行int类型会失败。
要求开发人员在编码过程中记住表数据类型,或进行类型转换显示不合理,何况表结构有可能会变动。
可以开启表结构自动检测,在执行SQL前把参数转换成与表结构对应的类型
这样就可以像MySQL一样随意了
ConfigTable.IS_AUTO_CHECK_METADATA = true;可以参考anyline-simple-jdbc-postgresql
前端未提交数据情况下清空一列值
默认情况下前端不传值时,执行update时并不更新这一列
如
DataRow row = entity("ID:id","REMARK:remark","NAME:name");
service.save(row);
这时如果没有提交remark参数值,或remark值为空时,并不更新REMARK列
但有些情况下是前端需要清空一个属性值,有以下种方式
1.提交一个"NULL"的值
2.指定必须更新的列
DataRow row = entity("ID:id","+REMARK:remark");
3.显示指定需要更新的列
service.update("HR_USER", row, "REMARK","NAME");
查询时设置主键
如果没有修改配置文件,默认以ID作为每个表的主键,也可以通过配置文件中的DEFAULT_PRIMARY_KEY来修改默认主键
但总有一些数据库设计表时没有主建
而有一些查询过程需要用到主键,比如MSSQL分页查询时会默认根据主键来排序 ROW_NUMBER OVER(ORDER BY ID)
这时如果需要根据其他列来排序可以在查询时指定
service.querys("HR_USER<AGE>", condition(true));
这样在分页时就以AGE作为排序依据
save与insert区别
save将根据主键是否有值来决定执行update还是insert
对于DataRow参数来说,save调用的insert与直接调用insert没有太大区别, 只是save多了一次判断
区别主要在于DataSet参数:save会循环操作数据库每次都会判断insert|update save 集合中的数据可以是不同的表不同的结构 insert 集合中的数据必须保存到相同的表,结构必须相同 insert 将一次性插入多条数据整个过程有可能只操作一次数据库 并 不考虑update情况 对于大批量数据来说 性能是主要优势
为什么有时update sql不执行
如果DataRow的值没有变化,则执行service.update时不会执行更新SQL
需要清空内容的列没有更新
默认情况下内容为空的列(“”或null) 不会出现在UPDATE SQL中,
可以通过以下方式强制更新指定列
1.指定更新列
service.update(row,"ID","NM");
2.设置成约定的值NULL(大写)
row.put("NM","NULL");//可以从前台页面输入
3.构造DataRow时指定
row = entityRow("+NM:nm","+CODE:code");
4.指定更新全部列
row.addAllUpdateColumns()
5.统一修改配置文件
<!-- 是否更新NULL列 -->
<property key="IS_UPDATE_NULL_COLUMN">true</property>
<!-- 是否更新空列 -->
<property key="IS_UPDATE_EMPTY_COLUMN">true</property>
其中4、5需要注意如果DataRow中有表中没有的列(如DataRow是从视图中查出来的),会导致SQL异常
这就需要人工将表中没有的列remove后再执行更新
condition()
AnylineController中提供了condition()用来接收客户端传值,并赋值给SQL,协助其构造查询条件,condition函数返回org.anyline.jdbc.config.ConfigStore
condition()主要用来:
1.通过HttpRequest构造ConfigStore用来实现复杂的数据库查询条件
2.自动封装分页参数分页参数
比较简单config(true)表示需要分页 config(10)表示分页并显式指定一页10行
由于历史版本的兼容condition,parseConfig,config三者实现的是完全一致的功能。
condition参数格式参考:约定格式
IN条件下多种参数格式的接收
对于标准的url格式 /list?id=1&id=2
以及标准的json格式 {id:[1,2]}
可以通过condition("ID:[id]")的形式接收
对于非标准格式如 /list?id=1,2
可以通过condition("ID:[split(id)]")的形式接收
最终都是生成SQL WHERE ID IN(1,2)
关于几种OR条件查询的情况
//以下三种格式,只有cd取值成功时,条件才生效
//当cd=1,id=2时 WHERE CODE = 1 OR CODE =2
//当cd=null,id=2时 条件不生效
//当cd=1,id=null时 WHERE CODE = 1
service.querys("HR_USER", condition("CODE:cd|id"));
//当cd=1时 WHERE CODE =1 OR CODE = 9
//当cd=null时 条件不生效
service.querys("HR_USER", condition("CODE:cd|{9}"));
//当cd=1时 WHERE CODE =1 OR CODE IS NULL
//当cd=null时 条件不生效
service.querys("HR_USER", condition("CODE:cd|{NULL}"));
//当type=1,dept=null时 WHERE TYPE_CODE = 1
//当type=1,dept=2时 WHERE TYPE_CODE =1 OR DEPT_ID =2
//当type=null,dept=2时 WHERE DEPT_ID = 2
service.querys("HR_USER", condition("TYPE_CODE:type|DEPT_ID:dept"));
//依次取c1,c2的值,如果c1取值成功则忽略c2,如果都失败则取默认值9
service.querys("HR_USER", condition("CODE:c1:c2:{9}"));
关于or
OR条件构造相对复杂
condition("NM:nm|NM:name"); 生成SQL WHERE NM ='z' OR NM = 'zh'
condition("NM:nm|name|user");
中有第一个nm取值成功当前条件才生效,如果nm取值失败后面的name,user也忽略,当前条件无效
经常会这样用:
condition("NM:nm|{1}"); 生成SQL WHERE NM = 'zh' OR NM = 1
condition("NM:nm|{NULL}"); 生成SQL WHERE NM = 'zh' OR NM IS NULL
如果是在java中生成OR条件
condition("NM:nm","AGE:20").or("SORT","1") 生成SQL WHERE NM = 'zh' AND AGE=20 OR SORT = 1
condition("NM:nm","AGE:20").ors("SORT","1") 生成SQL WHERE (NM = 'zh' AND AGE=20) OR SORT = 1 //表示将在此之前的所有条件合成一组后与当前条件OR
关于IN条件
IN一般需要提供一个数组赋值,接收时通过[key]的形式接收
service.query("HR_USER", condition("CODE:[code]"))
http://www.anyline.org?code=1&code=2&code=3
对应生成SQL
SELECT * FROM HR_USER WHERE CODE IN(1,2,3)
或
service.query("HR_USER", condition().addConditions("CODE", new ArrayList()));
关于必须条件
以“+”开头表示当前条件必须拼接
如condition("+NM:nm")
如果客户端提供了nm值,如http://localhost?nm=zh 则正常生成SQL:
SELECT * FROM TAB WHERE NM = 'zh'
如果没有提供nm值,如http://localhost或 http://local?nm= 则生成SQL:
SELECT * FROM TAB WHERE NM IS NULL
以"++"开头表示当前条件必须传值,否则整个SQL不执行
如果客户端提供了nm值,如http://localhost?nm=zh 同样正常生成SQL:
SELECT * FROM TAB WHERE NM = 'zh'
如果没有提供nm值,如http://localhost或 http://local?nm= 则整个SQL不执行,并返回长度为零的DataSet 同时会输入SQL日志[validate:false]
关于默认值
在接收http参数时,如果没有指定值则取其他值
service.querys("HR_USER",condition("NM:name:nm:user"));
如果未提供name值或name值为空,则依次根据nm,user值
http://www.anyline.org/user/l?name=zh&nm=zhang&user=z
生成对应SQL
SELECT * FROM HR_USER WHERE NM='zh'
http://www.anyline.org/user/l?name=&nm=zhang
生成对应SQL
SELECT * FROM HR_USER WHERE NM='zhang'
也可以通过常量赋值
service.querys("HR_USER",condition("NM:name:nm:user:{zz}"));
如果通过name,nm,user都未取到值则取常量值zz
http://www.anyline.org/user/l?name=zh&nm=zhang&user=z
生成对应SQL
SELECT * FROM HR_USER WHERE NM='zh'
http://www.anyline.org/user/l
生成对应SQL
SELECT * FROM HR_USER WHERE NM='zz'
关于like
http://127.0.0.1?nm=zh
service.querys("HR_USER",condition("NM:%nm%"));
生成对应SQL
SELECT * FROM HR_USER WHERE NM LIKE '%zh%'
service.querys("HR_USER",condition("NM:%nm"));
生成对应SQL
SELECT * FROM HR_USER WHERE NM LIKE '%zh'
service.querys("HR_USER",condition("NM:nm%"));
生成对应SQL
SELECT * FROM HR_USER WHERE NM LIKE 'zh%'
addCondition()
condition()用来接收前端参数,如果参数值不是来自前端面是在java中构造的。需要通过org.anyline.jdbc.config.ConfigStore提供的addCondition函数用来动态添加查询条件
ConfigStore一般通过condition()返回,如:
service.query("HR_USER", condition("NM:nm").addCondition(...));
addCondition提供了多个重载,常用以下3个
//一般会生成key=value的查询条件
public ConfigStore addCondition(String key, Object value);
//通过compare来指定比较符号如于小,小于,等于,between等参考org.anyline.jdbc.config.db.SQL.COMPARE_TYPE
//需要注意的是,如果需要构造in,not int, between条件,应该通过集合形式传参
public ConfigStore addCondition(COMPARE_TYPE compare, String key, Object value);
//如果value是一个集合则生成IN条件,否则与addCondition作用相同
public ConfigStore addConditions(String key, Object value);
另外还有
public ConfigStore addCondition(Config config);
public ConfigStore addCondition(String key, Object value, boolean overCondition, boolean overValue);
public ConfigStore addCondition(COMPARE_TYPE compare, String key, Object value, boolean overCondition, boolean overValue);
//这里同时提供key与var用来对应自定义SQL查询条件中的id与标签体中的变量
public ConfigStore addCondition(String key, String var, Object value);
如
<condition id="USER_AGE">AGE>{AGE_FR} AND AGE<{AGE_TO}</condition>//注意标签体中XML符号应该转义
addCondition("USER_AGE","AGE_FR","10").addCondition("USER_AGE","AGE_TO","20");
生成对应的SQL:WHERE AGE>10 AND AGE<20
COMPARE_TYPE枚举
public static enum COMPARE_TYPE{
EQUAL {public int getCode(){return 10;} public String getSql(){return " = ?";} public String getName(){return "等于";}},
GREAT {public int getCode(){return 20;} public String getSql(){return " > ?";} public String getName(){return "大于";}},
GREAT_EQUAL {public int getCode(){return 21;} public String getSql(){return " >= ?";} public String getName(){return "大于等于";}},
LESS {public int getCode(){return 30;} public String getSql(){return " < ?";} public String getName(){return "小于";}},
LESS_EQUAL {public int getCode(){return 31;} public String getSql(){return " <= ?";} public String getName(){return "小于等于";}},
IN {public int getCode(){return 40;} public String getSql(){return " IN ";} public String getName(){return "in";}},
LIKE {public int getCode(){return 50;} public String getSql(){return " LIKE ";} public String getName(){return "%like%";}},
LIKE_PREFIX {public int getCode(){return 51;} public String getSql(){return " LIKE ";} public String getName(){return "%like";}},
LIKE_SUBFIX {public int getCode(){return 52;} public String getSql(){return " LIKE ";} public String getName(){return "like%";}},
BETWEEN {public int getCode(){return 80;} public String getSql(){return " BETWEEN ? AND ? ";} public String getName(){return "区间";}},
NOT_EQUAL {public int getCode(){return 110;} public String getSql(){return " != ?";} public String getName(){return "不等于";}},
NOT_IN {public int getCode(){return 140;} public String getSql(){return " NOT IN ";} public String getName(){return "不包含";}};
public abstract String getName();
public abstract String getSql();
public abstract int getCode();
}
自定义SQL
针对一些视图无法实现的复杂的查询,如果是常用的可以创建数据函数或存储过程。
临时的可以自定义SQL来实现
SQL文件的根目录通过anyline-config.xml指定 <property key="SQL_STORE_DIR">/WEB-INF/classes/sql</property>
文件格式:
<?xml version="1.0" encoding="UTF-8"?>
<sqls>
<sql id="ROLE_LIST">
<title>用户角色列表</title>
<text>
SELECT
M.ID AS ID, M.NM AS NM,
CASE WHEN F.ID IS NULL THEN 0 ELSE 1 END AS CHK
FROM pw_role AS M
LEFT JOIN PW_USER_ROLE AS F
ON M.ID = F.ROLE_ID AND PW_USER_ID = {PW_USER_ID} <!--condition("PW_USER_ID:usr")或addCondition("PW_USER_ID",1001) -->
WHERE DEPT IN({DEPT})
AND GROUP_ID = ${GROUP_ID}
AND ${CONDITION_001}
</text>
<condition id="TEL">
M.TEL = ?
<!-- condition("TEL:tel")或addCondition("TEL","15800000000") -->
</condition>
<condition id="AGE">
M.AGE >= {AGE_MIN} AND M.AGE < {AGE_MAX}
<!--condition("AGE.AGE_MIN:min","AGE.AGE_MAX:max") -->
<!--或addCondition("AGE.AGE_MIN","10").addCondition("AGE.AGE_MAX","20") -->
</condition>
<condition id="FLAG" required="true">
<!--required表示当前条件必须,如没有值则默认取null -->
M.FLAG = ?
</condition>
<condition id="CODE">
M.AGE IN({CODE})
</condition>
<condition id="STATUS" static="true">
<!--static表示当前条件是静态条件不需要动态赋值 -->
M.DATA_STATUS IS NOT NULL
</condition>
<condition id="TYPE" static="true" test="USER_TYPE=2">
<!--test中是一个ognl表达式,表达示返回true时当前条件有效, -->
<!--condition("TYPE.USER_TYPE:type")或addCondition("TYPE.USER_TYPE","01") -->
M.DATA_STATUS IS NOT NULL
</condition>
<condition id="NM">
<!--condition("NM:nm")或addCondition(NM","zh") 注意这里赋值时不需要通配符% -->
M.NM LIKE '%?%' 或者M.NM LIKE '%{NM}%'
</condition>
<group>
M.NM
</group>
<order>
M.NM ASC
</order>
</sql>
</sqls>
{id} 或者 :id 格式用来标记占位符,在最终生成的SQL中通过?替换 ${id}
也可以通过${id} 或者 ::id 格式用来标记占位符,但最终生成SQL时,不会用?替换,而是直接用sql.replace("${id}",value)的形式生成
java中调用SQL时,通过condition("id:key")或addCondition("id","value")的形式赋值
如果当前文件path=/WEB-INF/classes/sql/hr/user/stat.xml, 则java中调用通过service.querys("hr.user.stat:ROLE_LIST",condition("id:key"));的方式调用
多数据源
同一个应用中操作多个不同的数据库
启动类上添加注解
@Import(org.anyboot.jdbc.ds.DynamicDataSourceRegister.class)
spring多数据源配置
在XML中配置
<bean id="ds_base" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="5" />
<property name="minIdle" value="20" />
<property name="maxActive" value="100" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 0" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 对于长时间不使用的连接强制关闭 -->
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="120" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<bean id="ds_sso" parent="ds_base" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="jdbc:mysql://127.0.0.1:3306/SSO?useUnicode=true&characterEncoding=UTF8" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="ds_api" parent="ds_base" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="jdbc:mysql://127.0.0.1:3306/API?useUnicode=true&characterEncoding=UTF8" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="ds" class="org.anyline.config.db.ds.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 指定lookupKey和与之对应的数据源 -->
<entry key="ds_sso" value-ref="ds_sso"></entry>
<entry key="ds_api" value-ref="ds_api"></entry>
</map>
</property>
<!-- 这里可以指定默认的数据源 -->
<property name="defaultTargetDataSource" ref="ds_sso" />
</bean>
<!-- JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds" />
</bean>
JAVA中三种方式调用
1.DataSourceHolder.setDataSource(String dataSource, boolean auto)
dataSource:对应配置文件中<entry key="ds_sso" value-ref="ds_sso"></entry>的key
auto:执行一次SQL后,是否自动还原为本次setDataSource执行之前的数据源
2.在AnylineService.query等方法指定数据源
service.query("<ds_api>表名");
需要注意的是,通过这种方式指定数据源,仅对本次操作有效,执行完本次操作后,数据源还原为上次设置的数据源
3.通过注解或拦截器执行DataSourceHolder.setDataSource
动态注册多数据源
在系统启动后通过java注册数据源
还是通过DataSourceHolder.addDataSource(String key, DataSource ds)添加新的数据源
DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://ip:port/api?useUnicode=true&characterEncoding=UTF8");ds.setUsername("root");ds.setPassword("root");//或者这样 如果要设置更多参数 放到map里String url = "jdbc:mysql://127.0.0.1:3306/sso?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";DataSourceHolder.reg("sso", "com.zaxxer.hikari.HikariDataSource", "com.mysql.cj.jdbc.Driver", url, "root", "root");
...更多参数设置
DataSourceHolder.addDataSource("api", ds);
调用时与XML配置的数据源调用方式相同
需要注意的是,添加数据源时如果key重复,会抛出异常
springboot多数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.driver=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/anyline?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.user=root
spring.datasource.user-name=root
spring.datasource.userName=root
spring.datasource.password=root
#spring.datasource.password=root
spring.datasource.primary.initial-size=10
spring.datasource.primary.max-idle=50
spring.datasource.primary.maxPoolSize=200
spring.datasource.primary.max-wait=3000
spring.datasource.primary.min-idle=50
spring.datasource.primary.validation-query=SELECT 1
spring.datasource.primary.test-on-borrow=true
spring.datasource.primary.test-while-idle=true
spring.datasource.primary.max-lifetime=600000
spring.datasource.primary.time-between-eviction-runs-millis=100000
#其他数据源
spring.datasource.list=crm,erp
spring.datasource.crm.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.crm.url=jdbc:mysql://127.0.0.1:3306/crm?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.crm.username=root
spring.datasource.crm.password=root
spring.datasource.erp.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.erp.url=jdbc:mysql://127.0.0.1:3306/erp?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.erp.username=root
spring.datasource.erp.password=root
切换数据源
//用<>表示数据源,执行完成后会自动切换回默认数据源
service.query("<crm>crm_customer");
service.query("HR_DEPARTMENT"); //这里查的还是默认数据源
service.query("<erp>mm_material");
try {
//动态注册一个数据源
//数据要设置更多参数 放到map里
String url = "jdbc:mysql://127.0.0.1:3306/sso?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
DataSourceHolder.reg("sso", "com.zaxxer.hikari.HikariDataSource", "com.mysql.cj.jdbc.Driver", url, "root", "root");
}catch (Exception e){
e.printStackTrace();
}
service.query("<sso>sso_user");
//固定数据源
DataSourceHolder.setDataSource("crm");
service.query("crm_customer"); //这一行执行完成后,数据源还是crm
service.query("crm_customer"); //这里的数据源还是crm
//切换回默认数据源
DataSourceHolder.setDefaultDataSource();
service.query("HR_DEPARTMENT");
缓存集成
约定格式
参数值⇢ 约定格式⇣ |
1 | 2 | 3 | 4 | 5 | 6 | 7 | |
code=0 | code= | code=0&code=1&cd=2&user=5 | code=0,1&cd=2&user=5
|
cd=2&cd=3 | code=0(密文) | cd=2(密文)&cd=3(密文) | ||
1 | CODE:code | CODE = 0 | 忽略 | CODE = 0
|
CODE = 0 | 忽略 | 忽略 | 忽略 |
2 | CODE:%code% | CODE LIKE '%0%' | 忽略 | CODE LIKE '%0%'
|
CODE LIKE '%0%'
|
忽略 | 忽略 | 忽略 |
3 | CODE:%code | CODE LIKE '%0' | 忽略 | CODE LIKE '%0'
|
CODE LIKE '%0' | 忽略 | 忽略 | 忽略 |
4 | CODE:code% | CODE LIKE '0%' | 忽略 | CODE LIKE '0%'
|
CODE LIKE '0%' | 忽略 | 忽略 | 忽略 |
5 | CODE:%code:cd%
(依次通过code,cd取值) |
CODE LIKE '%0%' | 忽略 | CODE LIKE '%0%'
|
CODE LIKE '%0%' | CODE LIKE '%2%' | 忽略 | 忽略 |
6 | CODE:%code:cd:${9}%
({}代表常量值,而不是参数key) |
CODE LIKE '%0%' | CODE LIKE '%9%' | CODE LIKE '%0%'
|
CODE LIKE '%0%' | CODE LIKE '%2%' | 忽略 | 忽略 |
7 | CODE:%code:cd | CODE LIKE '%0' | 忽略 | CODE LIKE '%0'
|
CODE LIKE '%0' | CODE LIKE '%2' | 忽略 | 忽略 |
8 | CODE:%code:cd:${9} | CODE LIKE '%0' | CODE LIKE '%9' | CODE LIKE '%0'
|
CODE LIKE '%0' | CODE LIKE '%2' | 忽略 | 忽略 |
9 | CODE:[code]
([]表示数组) |
CODE = 0 | 忽略 | CODE IN(0,1)
|
CODE IN(0,1) | 忽略 | 忽略 | 忽略 |
10 | CODE:[split(code)]
(调用默认类org.anyline.jdbc.config.DefaultPrepare的split预处理参数值) |
CODE = 1 | 忽略
|
CODE IN(0,1)
|
CODE IN(0,1)
|
忽略
|
忽略
|
忽略
|
11 | CODE:[org.ClassA.split(code)]
(指定完整的包名.类名.方法名) |
CODE = 1 | 忽略
|
CODE IN(0,1)
|
CODE IN(0,1)
|
忽略
|
忽略
|
忽略
|
12 | CODE:[code:cd] | CODE = 0 | 忽略 | CODE IN(0,1)
|
CODE IN(0,1) | CODE IN(2,3) | 忽略 | 忽略 |
13 | CODE:[cd+] | 忽略
|
忽略
|
CODE = 2
|
CODE = 2 | CODE IN(2,3) | 忽略
|
CODE IN(2,3) |
14 | CODE:[code:cd:${[6,7,8]}]
({[]}表示常量值是一个数组) |
CODE = 0 | CODE IN(6,7,8) | CODE IN(0,1)
|
CODE IN(0,1) | CODE IN(2,3) | 忽略 | 忽略 |
15 | CODE:[code:cd:${6,7,8}]
|
CODE = 0 | CODE IN(6,7,8) | CODE IN(0,1)
|
CODE IN(0,1) | CODE IN(2,3) | 忽略 | 忽略 |
16 | +CODE:code
(+表示当前条件取值失败后,默认取null值) |
CODE = 0 | CODE IS NULL | CODE = 0
|
CODE = 0 | CODE IS NULL | 忽略 | 忽略 |
17 | ++CODE:code
(++表示当前条件取值失败后,整个sql不执行) |
CODE = 0 | 不执行 | CODE = 0
|
CODE = 0 | 不执行 | 忽略 | 忽略 |
18 | CODE:>code | CODE > 0 | 忽略 | CODE > 0
|
CODE > 0 | 忽略 | 忽略 | 忽略 |
19 | CODE:>code:cd | CODE > 0 | 忽略 | CODE > 0
|
CODE > 0 | CODE > 2 | 忽略 | 忽略 |
20 | CODE:>code:${9} | CODE > 0 | CODE > 9 | CODE > 0
|
CODE >0 | CODE > 9 | CODE > 9 | CODE > 9 |
21 | CODE:code:cd | CODE = 0 | 忽略 | CODE = 2
|
CODE = 2 | CODE = 2 | 忽略 | 忽略 |
22 | CODE:code:cd:${9} | CODE = 0 | CODE = 9 | CODE = 0
|
CODE = 0 | CODE = 2 | 忽略 | 忽略 |
23 | CODE:code|cd
(只有code取值成功,当前条件才生效,注意与下一条的区别) |
CODE = 0 | 忽略 | CODE =0 OR CODE = 2
|
CODE =0 OR CODE = 2 | 忽略 | 忽略 | 忽略 |
24 | CODE:code|{NULL} | CODE = 0 OR CODE IS NULL | 忽略 | CODE = 0 OR CODE IS NULL
|
CODE = 0 OR CODE IS NULL | 忽略
|
忽略
|
忽略
|
25 | CODE:code|CODE:cd
(code与cd不相干,哪个有值取哪个) |
CODE = 0 | 忽略 | CODE = 0 OR CODE = 1
|
CODE = 0 OR CODE = 1 | CODE = 2 | 忽略 | 忽略 |
26 | CODE:code|CD:cd
(与上一条规则相同) |
CODE = 0 | 忽略 | CODE = 0 OR CD = 2
|
CODE = 0 OR CD = 2 | CD = 2 | 忽略 | 忽略 |
27 | CODE:code:cd|user
|
CODE = 0 | 忽略 | CODE = 0 OR CODE = 5
|
CODE = 0 OR CODE = 5 | CODE = 2 | 忽略 | 忽略 |
28 | CODE:code:cd|{9}
|
CODE = 0
|
忽略 | CODE = 0 OR CODE = 9
|
CODE = 0 OR CODE = 9 | CODE = 2 OR CODE = 9 | CODE = 9 | CODE = 9 |
29 | CODE:code+:${9}
(http参数值实际应该是密文) |
CODE = 9 | CODE = 9 | CODE = 9
|
CODE = 9 | CODE = 9 | CODE = 0 | CODE = 9 |
30 | CODE:code+:cd:${9}
(code需要密文,cd需要明文) |
CODE = 9 | CODE = 9 | CODE = 2
|
CODE = 2 | CODE = 2 | CODE = 0 | CODE = 9 |
31 | CODE:code+:cd+ | 忽略
|
忽略
|
忽略
|
忽略
|
忽略
|
CODE = 0 | CODE = 2 |
32 | CODE:code|CODE:cd|CD:cd|CD:code | CODE = 0 OR CD = 0 | 忽略 | CODE =0 OR CODE = 2 OR ID =0 OR ID = 2
|
CODE =0 OR CODE = 2 OR ID =0 OR ID = 2 | CODE =2 OR CD =2 | 忽略 | 忽略 |
33 | CODE:code:${9}|CD:cd:${9} | CODE = 0 OR CD = 9 | CODE = 9 OR CD = 9 | CODE = 0 OR CD = 2
|
CODE = 0 OR CD = 2 | CODE = 9 OR CD = 2 | CODE = 9 OR CD = 9 | CODE = 9 OR CD = 9 |
以上SQL在实际运行中以占位符?生成,类似CODE > '0'的条件实际是CODE > ?,java中通过 preapreStatement赋值,最终执行结果与数据类型有关
忽略:表示合成SQL时不拼接当前查询条件
不执行:表示整个SQL不执行,querys返回长度为0的DataSet,query返回null
"+"开头表示必须条件,如果没有值传则生成CODE IS NULL的条件(仅"="时有效,其他IN,>时,当前条件忽略)
“++”开头时,如果没有传值则整个SQL不执行,返回长度为零的DataSet
多表关联查询
首先Java代码中不建议使用多表查询。正常情况下应该由数据库负责人来提供相关的视图或存储过程。
常用格式如下,其中查询条件与其他查询格式一致
set = service.querys(TableBuilder.init().setTable("CRM_TENANT AS M")
.left("CRM_USER AS U","M.USER_ID = U.ID")
.build());
set = service.querys(TableBuilder.init("SD_ORDER AS M")
.left("CRM_USER AS U","M.USER_ID = U.ID")
.left("CRM_TENANT AS T","M.TENANT_ID = T.ID")
.build());
set = service.querys(TableBuilder.init("SD_ORDER(M.ID,U.NM,T.NM AS TENANT_NM) AS M")
.left("CRM_USER AS U","M.USER_ID = U.ID")
.left("CRM_TENANT AS T","M.TENANT_ID = T.ID")
.build()
,condition(true,"M.ID:od","TENANT_ID:tt"));
service.querys(TableBuilder.init()
.setTable("HR_USR(U.ID AS USER_ID,D.ID AS DEPT_ID)").setAlias("U")
.left("HR_DEPT D","U.DEPT_ID = D.ID").build()
, condition(true,"A.NM:%nm%"));
存储过程
//存储过程定义
CREATE PROCEDURE USER_REG (
IN _account varchar(10) ,
IN _password vachar(50) ,
INOUT _status_ int ,
OUT id_ varchar(50) ,
OUT msg_ varchar(50)
)
Procedure proc = new ProcedureImpl("过程名称");
//输入参数
proc.addInput("root"); //输入参数值
proc.addInput("000000");
//注册输出参数,根据输出参数个数调用
proc.regOutput("1"); //如果参数既是输入又是输出,需要调用regOutput同时指定默认值
proc.regOutput();
proc.regOutput();
//有返回值的存储过程,在执行之前调用一次
proc.regReturn();
//执行存储过程 这里只接收存储过程是否执行成功,并不接收执行存储过程返回结果,执行结果需要接收输出参数结果
boolean rtn = service.executeProcedure(proc);
//接收输出参数与返回值,result中先保存返回值,再依次保存输出参数
List<Object> list = proc.getResult();
//查询存储过程
DataSet set = service.queryProcedure(proc);
//也可以同时指定输入参数值
DataSet set = service.queryProcedure(proc, "1","2");
如果通过query接收存储过程的结果集,需在在过程中返回结果集而不是返回一个值
以SQL Server为例
应该是这样:
ALTER PROCEDURE [dbo].[PRO_TEST]
@a int
AS
BEGIN
SELECT * FROM ABM120T
END
而不是这样:
这样返回的应该通过execute(Procedure proc)执行,然后通过proc.getResult()获取返回值
ALTER PROCEDURE [dbo].[PRO_TEST]
@a int
AS
BEGIN
RETURN 0;
END
常用数据库操作
service.query("HR_USER");
//SELECT * FROM HR_USER
service.query("HR_USER(ID,NM)");
//SELECT ID,NM FROM HR_USER
service.query("HR_USER","ORDER BY ID");
//SELECT * FROM HR_USER ORDER BY ID
service.query("CRM_USRE(DISTINCT AGE)");
//SELECT DISTINCT AGE FROM HR_USER
service.query("HR_USER(ROLE_ID,COUNT(ID) AS QTY)", "GROUP BY ROLE_ID");
//SELECT ROLE_ID,COUNT(ID) AS QTY FROM HR_USER ORDER BY ROLE_ID
service.qurey("HR_USER(ID,NM,{CASE WHEN AGE >20 THEND 1 ELSE 2 END AS AGE_SORT})");//如果列很复杂用{}区分开
//SELECT ID,NM, CASE WHEN AGE >20 THEND 1 ELSE 2 END AS AGE_SORT FROM HR_USER
//多表查询
service.querys(TableBuilder.init().setTable("CRM_TENANT AS M")
.left("HR_USER AS U","M.USER_ID = U.ID")
.build());
service.querys(TableBuilder.init("SD_ORDER AS M")
.left("HR_USER AS U","M.USER_ID = U.ID")
.left("CRM_TENANT AS T","M.TENANT_ID = T.ID")
.build());
service.querys(TableBuilder.init("SD_ORDER(M.ID,U.NM,T.NM AS TENANT_NM) AS M")
.left("HR_USER AS U","M.USER_ID = U.ID")
.left("CRM_TENANT AS T","M.TENANT_ID = T.ID")
.build()
,condition(true,"M.ID:od","TENANT_ID:tt"));
更多建议: