# DAO数据层开发
# 数据层开发示例
数据层用于与数据库进行CRUD交互,本节按照一个开发示例进行演示数据层的开发模式。
# 第一步:建表
建表规范见本页面后续章节,以MySQL为例:
- 必须定义主键,统一命名为ID,BIGINT类型,默认通过Java的UUID生成
- 创建人、创建单位默认都指向对应表的主键,所以数据类型也是BIGINT
- 文本内容默认使用VARCHAR,根据应用场景定义字符长度
- 未知长度(允许用户输入大量文本)可以使用LONGTEXT,但注意SQL性能
- 日期时间使用DATETIME,日期使用DATE
- 数据状态(删除、未删除)一般对应0和1,使用SMALLINT即可
/*==============================================================*/
/* Table: SHOWBAR_INFO DBType:MySQL */
/*==============================================================*/
CREATE TABLE SHOWBAR_INFO
(
ID BIGINT NOT NULL,
CREATE_USER_ID BIGINT,
ACCOUNT_ID BIGINT,
CREATE_TIME DATETIME,
UPDATE_USER_ID BIGINT,
UPDATE_TIME DATETIME,
CREATE_FROM SMALLINT,
STATUS SMALLINT,
SHOWBAR_NAME VARCHAR(255),
START_DATE DATETIME,
END_DATE DATETIME,
ADDRESS VARCHAR(255),
SUMMARY VARCHAR(1024),
SETTOP_TIME DATETIME,
VIEW_NUM INT,
LIKE_NUM INT,
IMG_NUM INT,
COMMENT_NUM INT,
COVER_PICTURE BIGINT,
ORDER_NUM INT,
LAST_IMAGE_ID BIGINT,
ORDER_TIME DATETIME,
PRIMARY KEY (ID)
);
# 第二步:新增索引
开发需要预估表的未来数据量:如果未来会超过5万条,则要提前考虑建立规范、有效的数据库索引。
由于本案例表数据不大,CREATE_USER_ID字段经常被当做SQL条件,对该字段则建立简单的索引即可
CREATE INDEX IDX_SHOW_MYCREATED ON SHOWBAR_INFO(CREATE_USER_ID);
# 第三步:定义hbm
平台使用Hibernate框架与数据库进行交互,需要定义hbm文件和与数据库字段相对应的POJO对象。代码位置如下:
com
seeyon
apps
show
po
ShowbarInfo.java
ShowbarInfo.hbm.xml
在com.seeyon.apps.show.po package下新增ShowbarInfo.hbm.xml文件,该文件以XML的格式定义数据库表字段与Java POJO的关系:
注意:只允许使用Hibernate最传统的字段映射特性,不要使用one-to-many这类多表关联特性!
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.seeyon.apps.show.po.ShowbarInfo" table="showbar_info">
<id name="id" type="java.lang.Long">
<column name="ID" />
<generator class="assigned" ></generator>
</id>
<property name="createUserId" type="java.lang.Long">
<column name="CREATE_USER_ID" >
<comment>秀吧创建人ID</comment>
</column>
</property>
<property name="accountId" type="java.lang.Long">
<column name="ACCOUNT_ID" >
<comment>所属单位ID</comment>
</column>
</property>
<property name="createTime" type="java.util.Date">
<column name="CREATE_TIME" length="19" >
<comment>创建时间</comment>
</column>
</property>
<property name="updateUserId" type="java.lang.Long">
<column name="UPDATE_USER_ID">
<comment>最近修改人ID</comment>
</column>
</property>
<property name="updateTime" type="java.util.Date">
<column name="UPDATE_TIME" length="19">
<comment>最近修改时间</comment>
</column>
</property>
<property name="createFrom" type="java.lang.Integer">
<column name="CREATE_FROM" >
<comment>创建自(PC、M1)</comment>
</column>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS" >
<comment>状态标识:0删除,1正常,2系统预制秀</comment>
</column>
</property>
<property name="showbarName" type="java.lang.String">
<column name="SHOWBAR_NAME" >
<comment>秀吧名称</comment>
</column>
</property>
<property name="startDate" type="java.util.Date">
<column name="START_DATE" length="10">
<comment>秀吧开始时间</comment>
</column>
</property>
<property name="endDate" type="java.util.Date">
<column name="END_DATE" length="10">
<comment>秀吧结束时间</comment>
</column>
</property>
<property name="address" type="java.lang.String">
<column name="ADDRESS">
<comment>秀吧活动地址</comment>
</column>
</property>
<property name="summary" type="java.lang.String">
<column name="SUMMARY" length="1000">
<comment>秀吧简介</comment>
</column>
</property>
<property name="settopTime" type="java.util.Date">
<column name="SETTOP_TIME" length="19">
<comment>置顶时间(未置顶则为空)</comment>
</column>
</property>
<property name="viewNum" type="java.lang.Integer">
<column name="VIEW_NUM" >
<comment>浏览次数</comment>
</column>
</property>
<property name="likeNum" type="java.lang.Integer">
<column name="LIKE_NUM" >
<comment>点赞次数</comment>
</column>
</property>
<property name="imgNum" type="java.lang.Integer">
<column name="IMG_NUM" >
<comment>照片总数</comment>
</column>
</property>
<property name="commentNum" type="java.lang.Integer">
<column name="COMMENT_NUM" >
<comment>评论总数</comment>
</column>
</property>
<property name="coverPicture" type="java.lang.Long">
<column name="COVER_PICTURE" >
<comment>封面图片ID(对应show_imgae表)</comment>
</column>
</property>
<property name="orderNum" type="java.lang.Integer">
<column name="ORDER_NUM">
<comment>排序号</comment>
</column>
</property>
<property name="lastImageId" type="java.lang.Long">
<column name="LAST_IMAGE_ID">
<comment>主题最新图片ID</comment>
</column>
</property>
<property name="orderTime" type="java.util.Date">
<column name="order_time" length="19" >
<comment>主题排序时间</comment>
</column>
</property>
</class>
</hibernate-mapping>
在上一步完成基础上,在同级package下新建对应POJO,继承平台的BasePO,代码如下:
public class ShowbarInfo extends BasePO{
private static final long serialVersionUID = 2180154441235743764L;
private Long createUserId; //秀吧创建人ID
private Long accountId; //所属单位ID
private Date createTime; //创建时间
private Long updateUserId; //最近修改人ID
private Date updateTime; //最近修改时间
private Integer createFrom; //创建自(PC、M1)
/**
* 0删除 状态去掉,直接物理删除
*/
private Integer status; //状态标识:0删除,1正常,2系统预制秀
private String showbarName; //秀吧名称
private Date startDate; //秀吧开始时间
private Date endDate; //秀吧结束时间
private String address; //秀吧活动地址
private String summary; //秀吧简介
private Date settopTime = ShowConstants.DEFAULT_TIME; //置顶时间(未置顶则为空)
private Integer viewNum; //浏览次数
private Integer likeNum; //点赞次数
private Integer imgNum; //照片总数
private Integer commentNum; //评论总数
private Long coverPicture; //封面图片ID(对应show_imgae表)
private Integer orderNum; //序号
private Long lastImageId; //主题最新图片ID
private Date orderTime; //秀吧排序时间 V5 V7.0SP1添加
// Constructors
/** default constructor */
public ShowbarInfo() {
}
getter...
setter...
}
# 第四步:DAO层接口和实现
DAO层就是建立数据库互通的代码层,代码CRUD均要求在DAO层完成,我们遵守Spring IOC容器规范,定义接口+实现的Dao层模型进行代码维护!
public interface ShowbarInfoDao {
/**
* <p>Description: 创建一个秀吧</p>
*
* @param showbarInfo 秀吧PO
* @throws BusinessException
*/
public void save(ShowbarInfo showbarInfo) throws BusinessException;
/**
* <p>Description:更新秀吧 </p>
*
* @param showbarInfoId 秀吧PO
* @throws BusinessException
*/
public void update(ShowbarInfo showbarInfo) throws BusinessException;
/**
* <b>获取秀吧操作数据</b><br>
*
* @param dataIds 秀吧的Id
* @return
* @throws BusinessException
*/
@SuppressWarnings("rawtypes")
public List<Map> findShowbarNums(Long[] dataIds) throws BusinessException;
}
平台提供了DBAgent和JDBCAgent的封装进行数据库操作:
- DBAgent对应的是Hibernate标准API那一套,基于PO实体进行增删改,基于HQL进行数据查询
- JDBCAgent对应的是Java JDBC那一套,基于JDBC SQL进行增删改查
一般简单的CUD,我们都使用DBAgent(即Hibernate)那一套实现,Hibernate在多数据库兼容上做到了尽量的简单、易用。
而一般复杂查询,尤其是多表联查,我们要求尽量使用JDBCAgent,即传统的SQL开发。在高并发下,SQL效率比HQL要高!
public class ShowbarInfoDaoImpl implements ShowbarInfoDao {
@Override
public void save(ShowbarInfo showbarInfo) throws BusinessException {
DBAgent.save(showbarInfo);
}
@Override
public void update(ShowbarInfo showbarInfo) throws BusinessException {
DBAgent.update(showbarInfo);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public List<Map> findShowbarNums(Long[] dataIds) throws BusinessException {
StringBuilder hql = new StringBuilder();
hql.append("SELECT New Map(");
hql.append("id as id,");
hql.append(" viewNum as viewNum,");
hql.append(" likeNum as likeNum,");
hql.append(" imgNum as imgNum,");
hql.append(" commentNum as commentNum ");
hql.append(") FROM ");
hql.append(ShowbarInfo.class.getCanonicalName());
hql.append(" WHERE id in (:ids)");
Map<String, Object> param = new HashMap<String, Object>();
param.put("ids", dataIds);
return DBAgent.find(hql.toString(), param);
}
}
# 第五步:注册DAO到Spring容器
按照插件化开发规则,找到自己的插件目录,将Dao注入到spring中,示例如:src\main\webapp\WEB-INF\cfgHome\plugin\show\spring\spring-show-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="showbarInfoDao" class="com.seeyon.apps.show.dao.ShowbarInfoDaoImpl"></bean>
</beans>
# 第六步:Manager层调用
最后Dao被Manager调用,示例如下:
注:Dao不要被Controller调用,我们的三层依赖链是:Controller->Manager->Dao
public class ShowbarInfoManagerImpl implements ShowbarInfoManager {
private static Log logger = CtpLogFactory.getLog(ShowbarInfoManagerImpl.class);
private ShowbarInfoDao showbarInfoDao;
@Override
public void save(ShowbarInfo showbarInfo) throws BusinessException {
// 伪代码示例:这里展示的是Manager一个方法可以调用多个Dao的方法
// 并且如果这个manager的方法受事务管理,则如下任务Dao的增删改出现异常就会全部被回滚
showbarInfoDao.findShowbarNums(xxx);
showbarInfoDao.getById(xxx);
showbarInfoDao.save(showbarInfo);
// 伪代码示例:manager中的方法还可以嵌套调用别的manager方法
this.update(showbarInfo);
}
}
以上就是完整的数据库持久层开发示例,如有不清楚的地方,开发过程中,可以参考标准产品的代码。
# 数据层工具类详解
# 分页对象FlipInfo
在调用数据库封装方法前,先介绍平台提供的分页对象,这个在数据库操作中非常常见。
在开发过程中,我们可能会看到如下的代码写法,这里的FlipInfo是一个分页对象,用于告诉平台取第几页数据,并且还可以根据参数配置来确定是否执行一次count(*)求和操作,并且将结果集放入到FlipInfo的data属性中,一个对象多个能力:
public FlipInfo findResult(Map<String, Object> params) {
FlipInfo flipInfo = new FlipInfo();
// 无需执行count(*)返回求和数据
flipInfo.setNeedTotal(false);
// 一次取10条数据
flipInfo.setSize(10);
// 取结果中的第二页数据:即忽略前10条数据,取11~20的数据
flipInfo.setPage(2);
DBAgent.find("select new Org(id,title) from Org where orgname like :orgname order by id", params, flipInfo);
return flipInfo;
}
下面是FlipInfo分页对象的关键属性:
public class FlipInfo implements Serializable {
private List data; //查询结果集会放入此对象中
private int page = 1; //分页参数:取第几页数据
private int size = 20;//分页参数:一次取多少条数据
private boolean needTotal = true;//求和参数,默认true,表示默认会先执行count(*)求出结果集总数,再执行select分页查询需要的结果集
private int total = 0;//跟needTotal同生
}
最佳实践:如果你只需要分页查询出结果数据,则一定要设置flipInfo.setNeedTotal(false);这样能减少一条count(*)的SQL。
FlipInfo跟前端的Gird组件绑定较深,前端Gird表格组件的整个翻页逻辑与之一一对应,实用性很高。
# 数据操作DBAgent
DBAgent是CTP平台封装的基于Hibernate的数据库操作工具类,DBAgent功能接近于org.hibernate.Session,可以进行save、delete、update、query。DBAgent包装了具有V5特性的能力,更方便我们应用开发。
# 常用API
单条数据增删改查接口:
DBAgent.save(myPO); //新增保存PO实体对象
DBAgent.delete(myPO); //删除PO实体对象(myPO中的主键ID不能为空)
DBAgent.update(myPO);//更新PO实体对象
DBAgent.get(MyPO.class,id);//获取单条数据:根据主键ID查询PO实体对象
/**不推荐接口调用**/
@Deprecated
DBAgent.saveOrUpdate(myPO); //从数据库判断数据是否存在,存在则更新,不存则新增,此方法不推荐,开发过程一定要分清楚:新增就调用新增方法、更新就调用更新方法
DBAgent.merge(myPO);// 同saveOrUpdate
批量数据增、删、改接口:
DBAgent.saveAllForceFlush(pos);//批量新增插入PO对象数据,每1000条自动提交到数据库
DBAgent.saveAll(pos, true);//作用同saveAllForceFlush
DBAgent.deleteAll(pos);//批量删除PO对象数据
DBAgent.updateAll(pos);//批量修改PO对象数据
/**不推荐接口调用**/
DBAgent.saveAll(pos);//批量新增插入PO对象数据,与saveAllForceFlush的差异是:只有所有数据写入到缓冲区才提交,此法数据量成千上万时易引发性能问题
另外,如果需要根据某些特定条件进行批量更新或删除操作,同样推荐通过HQL进行数据批量更新、删除,此法类似于SQL的删除和更新,效率也很高:
DBAgent.bulkUpdate("delete from Org where orgid>? and orgid<?", 200L, new Long(210));
DBAgent.bulkUpdate("update from Org set orgname=? where orgid>? and orgid<?", "测试", 210L, new Long(220));
单表查询语句,根据特定条件查询结果集可以使用Hibernate的HQL,DBAgent同样封装了相关接口。
/**单表查询推荐使用HQL语法,并且select结果要明确,不要直接select * from查询全部**/
//根据传入的HQL进行查询操作,如果返回数据量过大不允许使用
List result = DBAgent.find("select id,title from Org");
//根据传入的HQL和命名参数进行查询操作,如果返回数据量过大不允许使用
List<Map> result = DBAgent.find("select new Map(o.id as id,o.title as title) from Org o where o.orgname like :orgname", params);
//根据传入的HQL和命名参数进行翻页查询操作,需要构造FlipInfo翻页信息类
List<Org> result = DBAgent.find("select new Org(id,title) from Org where orgname like :orgname", params, flipInfo);
// 平台提供了获取结果集总数的接口,如下HQL会被平台解析转换为count语句
int count = DBAgent.count("select id,title from Org");
int count = DBAgent.count("select new Map(o.id as id) from Org o where o.orgname like :orgname", params);
// 平台同时也提供了判断是存在结果数据的接口,如下HQL会被平台解析只获取1条结果集,如果数据>=1则表示存在
boolean isExist = DBAgent.exists("select id,title from Org");
boolean isExist = DBAgent.exists("select new Map(o.id as id) from Org o where o.orgname like :orgname", params);
关于HQL中参数传递的最佳实践: 使用:var的占位符形式进行参数传递,通过Map将变量放入参数对象中即可。
/**
*本例子几乎包含了所有场景的参数传递:包括如何使用LIKE传参,如何向IN中传参,如何进行日期比较等
**/
public void find(String name, Long memberId, Date startTime, List<Long> dataId, FlipInfo flipInfo) throws BusinessException {
String hql = "select new map(id,name,startTime) from Org where memberId = :memberId and startTime > :startTime and dataId in (:dataId) and name like :name";
Map<String,Object> params = new HashMap<>();
params.put("name","%" + SQLWildcardUtil.escape(name) + "%"); //注意:调用SQLWildcardUtil.escape过滤危险字符,防止SQL攻击
params.put("memberId",memberId); // Long类型就传入Long对象
params.put("startTime",startTime); // 日期类型就传入Date对象
params.put("dataId",dataId); // SQL IN就传输List或Array
DBAgent.find(hql,params,flipInfo);
}
内存分页: 此接口默认不推荐,在“情非得已”的时候使用!
原则上,我们要求通过HQL、SQL查询的数据就要进行分页,即一次只能查出xx条数据,这样才能保障性能!但是如果应用上不满足一条SQL解决问题,需要查询一批结果集进行内存运算之后再分页,则可以在最后一步调用如下方法进行分页。
DBAgent.memoryPaging(dataList, flipInfo);
# 禁忌操作
1)不推荐使用"select * from table"和"from table"的操作,要求按需获取数据!
2)多表关联不要使用HQL,需要改用SQL以保障性能
/**
1)不推荐HQL写法:
下面写法是Hibernate“引以为傲”的特性,但是这样编写会将表字段全部查询出来,如果大表会出现严重性能问题,我们需要遵循“需要什么结果就查询什么数据原则”,不要动不动就查询全部结果集
同时此法还有一个持久化缓存的问题,用此法会导致开发遇到很多不可控的坑,后面会介绍!
**/
List<Org> result = DBAgent.find("from Org");
List<Org> result = DBAgent.find("from Org where orgname like :orgname", params);
/**2)不推荐使用Hibernate完成多表关联,多表关联请使用SQL以提升性能**/
DBAgent.find("from Org o,User u where o.userId=u.id");
/**
3)同样不推荐使用Hibernate的如下封装特性,只要返回整个对象的都不再推荐
**/
//根据queryName进行查询操作,如果返回数据量过大不允许使用
DBAgent.findByNamedQuery(queryName);
//根据queryName和命名参数进行查询操作,如果返回数据量过大不允许使用
DBAgent.findByNamedQuery(queryName, params);
//根据queryName和命名参数进行翻页查询操作,需要构造FlipInfo翻页信息类
DBAgent.findByNamedQuery(queryName, params, flipInfo);
//根据queryName和ValueBean进行查询操作,Bean中的属性名将作为HQL中的命名参数,如果返回数据量过大不允许使用
DBAgent.findByNamedQueryAndValueBean(queryName, valueBean);
//根据queryName和ValueBean进行翻页查询操作,Bean中的属性名将作为HQL中的命名参数,需要构造FlipInfo翻页信息类
DBAgent.findByNamedQueryAndValueBean(queryName, valueBean, flipInfo);
3)巨坑:通过"from table"或DBAnget.get()获取的对象不要进行set操作,此举会导致Hibernate自动执行update更新:
public List<Org> find(Map params){
String hql = "from Org where xxx";
List<Org> result = DBAgent.find(hql,params);
for (Org org: result) {
// Bad code:执行from table的hql结果集会被Hibernate缓存,如果使用set导致属性变化,Hibernate会隐式执行update更新数据库
org.setName("newName");
org.setStartTime(new Date());
// 上面代码会导致Hibernate隐式执行:update Org set name='newName',startTime=new date()
}
return result;
}
public Org get(Long id){
Org org = DBAgent.get(id,Org.class);
// Bad code:使用get获取的结果会被Hibernate缓存,如果使用set导致属性变化,Hibernate会隐式执行update更新数据库
org.setName("newName");
org.setStartTime(new Date());
// 上面代码会导致Hibernate隐式执行:update Org set name='newName',startTime=new date()
return org;
}
4)判断是否存在结果数据不允许使用DBAgent.find(hql).size()的形式,要求使用DBAgent.exists(hql)方法:
public boolean isExsist(){
// Bad code:判断数据是否存在不应该通过查询出结果集结果再通过size()大小去判断
return DBAgent.find("select id from Org").size() > 0 ? true : false;
// Nice code:使用平台封装的exists方法是最高效的写法,此法是取第1页第1条数据出来,极大提升性能
return DBAgent.exists("select id from Org");
}
# 原生SQL-JDBCAgent
前面DBAgent是CTP平台封装,提供给开发利用Hibernate能力执行数据库操作的工具类。
而如果开发需要使用标准的JDBC SQL开发则需要另一个工具类:JDBCAgent是CTP平台封装,提供给开发执行SQL使用的工具类。
一个规范的JDBCAgent写法示例如下,设计创建JDBCAgent、调用Agent方法、关闭JDBCAgent这几步操作:
// 创建JDBCAgent
JDBCAgent agent = new JDBCAgent();
try {
// 调用相关JDBC接口执行SQL
agent.execute("update org_table set title = xxx");
} catch (Exception e) {
// 处理异常
logger.error("",e);
} finally {
// 必做:关闭JDBCAgent连接
agent.close();
}
# 常用API
创建JDBCAgent实例: 前面示例可以看到通过new JDBCAgent来创建对象实例。
自V7.1SP1开始,平台提供了多种构造函数创建JDBCAgent实例不同构造函数代表不同意思。
/**
* 使用Spring管理的数据库连接构造JDBCAgent
* --------------------------------------------------------------
* @deprecated 自7.1SP1版本,推荐使用以下方法:
* @see #JDBCAgent(boolean, boolean)
* --------------------------------------------------------------
*/
@Deprecated
public JDBCAgent() {
this(false);
}
/**
* 传入true时将使用原始的数据库连接构造JDBCAgent,false则使用Spring管理的数据库连接
* @param useNative 是否使用原始的数据库连接构造JDBCAgent
* --------------------------------------------------------------
* @deprecated 自7.1SP1版本,推荐使用以下方法:
* @see #JDBCAgent(boolean, boolean)
* --------------------------------------------------------------
*/
@Deprecated
public JDBCAgent(boolean useNative) {
this(useNative, false);
}
/**
* 获取JDBCAgent实例
* @param useNative 传入true时将使用原始的数据库连接构造JDBCAgent,false则使用Spring管理的数据库连接
* @param autoClose 传入true则由平台托管关闭连接,传入false则必须由开发通过.close()显性关闭连接
*/
public JDBCAgent(boolean useNative, boolean autoClose)
对于不熟悉CTP特性的开发,推荐如下两种写法,必然不会有问题:
// 推荐第一种:创建JDBCAgent
JDBCAgent agent = new JDBCAgent();
try {
// 调用相关JDBC接口执行SQL
agent.execute("update org_table set title = xxx");
} catch (Exception e) {
// 处理异常
logger.error("",e);
} finally {
// 必做:关闭JDBCAgent连接,不关闭会造成连接池泄漏
agent.close();
}
// 推荐第二种:创建JDBCAgent(false:默认使用Spring的连接,false:由开发显性调用.close()关闭连接)
JDBCAgent agent1 = new JDBCAgent(false,false);
try {
// 调用相关JDBC接口执行SQL
agent1.execute("update org_table set title = xxx");
} catch (Exception e) {
// 处理异常
logger.error("",e);
} finally {
// 必做:关闭JDBCAgent连接,不关闭会造成连接池泄漏
agent1.close();
}
调用JDBCAgent常用方法:
特别注意:jdbcAgent.resultSetToList()和jdbcAgent.findByPaging()最终返回的结果集参数key都是小写,那是底层做了转换!而如果jdbcAgent.resultSetToList(false)则会返回你设置的参数格式,但这个有坑:Oracle、达梦默认返回的是强制全部大写,所以不建议使用jdbcAgent.resultSetToList(false)。
// 直接执行SQL
int resultCount = jdbcAgent.execute("update org_table set title = xxx");
// 带参数调用SQL
jdbcAgent.execute("update org_table set title =? and data_id=?",Arrays.asList(new Object[]{title,dataId}));
// 类Hibernate HQL传参写法,带参数调用SQL
Map<String, Object> params = new HashMap<>();
params.put("title",title);
jdbcAgent.executeNamedSql("update org_table set title = :title",params);
// 执行查询语句SQL:需要做两步,第一步execute(),第二步resultSetToList或resultSetToMap返回结果集
jdbcAgent.execute("select id,title from org_table where title =?",title);
List<Map<String,Object>> listResult = jdbcAgent.resultSetToList();
// 执行查询语句SQL:需要做两步,第一步execute(),第二步resultSetToList或resultSetToMap返回结果集
jdbcAgent.execute("select id,title from org_table where title =?",title);
Map<String,Object> mapResult = jdbcAgent.resultSetToMap();
// 下方式执行SQL查询即可一次返回结果
Map<String, Object> params1 = new HashMap<>();
params1.put("title",title);
FlipInfo resultFlipInfo = jdbcAgent.findNameByPaging("select id,title from org_table where title = :title",params1,flipInfo);
FlipInfo resultFlipInfo1 = jdbcAgent.findByPaging("select id,title from org_table where title = ?",Arrays.asList(new Object[]{title}),flipInfo);
JDBCAgent同样提供了的批量增删改方法:
/**
* 批量执行SQL
* @param params [{'标题',123456},{'标题2',78901},{'标题3',98777}]
*/
public void batch(List<Object[]> params){
JDBCAgent jdbcAgent = null;
try {
jdbcAgent = new JDBCAgent();
jdbcAgent.batch1Prepare("update org_table set title = ? where id = ?");
jdbcAgent.batch2Add(params);
jdbcAgent.batch3Execute();
} catch (Exception e) {
logger.error("",e);
} finally {
jdbcAgent.close();
}
}
# 禁忌操作
JDBCAgent的禁忌相对DBAgent少很多,主要注意三个点:
// 1)一定一定一定要关闭连接,不执行如下close很容易引起客户环境连接池泄漏
jdbcAgent.close();
// 2)参数严禁直接拼接在SQL后面,需要以参数变量传入防止SQL注入:
String sql = "select id,title from org_table where id="+id;// Bad code:SQL注入
String sql = "select id,title from org_table where id= ?";// Nice code
// 3)不要使用select *查询全部结果集,务必按需查找结果集
String sql = "select * from org_table where id= ?";// Bad code
String sql = "select id,title from org_table where id= ?";// Nice code
# 扩展阅读
# JDBCAgent和DBAgent使用场景
# DBAgent使用场景
对数据执行新增、修改、删除操作,我们推荐使用DBAgent即Hibernate封装的能力来完成:save、saveAllForceFlush等方法能很好完成数据的写入。
如果你担心性能,也可以通过HQL进行批量修改、删除:delete from Org where id in (:ids);
对于单表的查询,我们同样推荐使用DBAgent进行操作,但注意查看[DBAgent禁忌规则],良好的编码习惯才能保证系统稳定运行。
# JDBCAgent使用场景
如果涉及多表关联查询,我们一概推荐使用JDBC SQL来完成,经过多个有效场景验证,多表关联的JDBC SQL比Hibernate HQL性能要强几倍甚至几十倍!
# AbstractHibernateDao
这是早期平台提供的:纯Hibernate Entity DAO基类,不带扩展的分页函数。通过泛型,子类无需扩展任何函数即拥有完整的CRUD操作。
继承AbstractHibernateDao之后,Dao层实现无需使用DBAgent,可以直接调用预埋的CRUD方法使用。
建议:如无必要,没必要使用此方案,推荐使用常规的JDBCAgent、DBAgent。
public class ShowDaoImpl extends AbstractHibernateDao<ShowbarInfo> implements ShowDao {
@Inject
private OrgManager orgManager;
@Override
public int countNewsShowbarList(User user, Map<String, Object> filter) throws BusinessException {
NamedQueryCondition condition = this.getNewsShowbarList(user, filter);
return super.count(condition.getNamedQuery(), "showbarInfo.id", true, condition.getParam());
}
@Override
@SuppressWarnings("unchecked")
public List<ShowbarInfo> findNewsShowbarList(int start, int size, User user, Map<String, Object> filter) throws BusinessException {
NamedQueryCondition condition = this.getNewsShowbarList(user, filter);
return super.find(condition.getNamedQuery(), start, size, condition.getParam());
}
}
# CTP平台事务管理机制
1)事务中,有些数据库对表的DDL会默认提交,如oracle,所以在一个事务中的操作,最好能区分开。
2)目前一个“空”事务中,使用DBAgent来对对象进行修改后,由于hibernate判断本次操作在事务中,所以暂缓刷新到数据库,而再使用DBAgent的find命令,由于判断当前事务为空事务,所以不会触发刷新到数据库,而是直接查询数据库,从而查询为空。
对于这种情况,一种方法是在修改或者新增的时候,手动调用dbagent的commit操作;二种方法是使用dbagent直接对对象的get操作。
V5平台要求使用全系统统一命名方法控制事务,即DAO层方法是下面开头则会被相应事务传播机制所控制:
有事务的方法命名(REQUIRED)
save*
insert*
delete*
update*
trans*
test*
import*
add*
create*
无事务的方法命名(SUPPORTS)
is*
check*
find*
get*
select*
list*
query*
on*
copy*
随着时间的迁移,平台的事务管理也在变化,当前版本的详细控制可参考配置文件:/WEB-INF/cfgHome/spring/spring-default.xml
# 使用注解控制事务
7.0以上版本支持注解配置事务,主要用于性能优化,消除不必要的事务,注解和XML同时配置则注解优先。
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = com.seeyon.ctp.common.exceptions.BusinessException.class)
@Override
public void updateETagDate(String category, String key) {
}
# 数据库规范
不同数据库字段映射关系:
MYSQL | ORACLE | SQLSERVER | POSTGRESQL | DM | KINGBASE | OSCAR | GBASE | HBM文件 | POJO类型 | 场景 |
---|---|---|---|---|---|---|---|---|---|---|
BIGINT | INTEGER | BIGINT | INT8 | BIGINT | INT8 | BIGINT | BIGINT | long | java.lang.Long | 主键 |
DATE | DATE | DATETIME | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | timestamp | java.util.Date | 日期 |
DATETIME | DATE | DATETIME | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | TIMESTAMP(0) | timestamp | java.util.Date | 日期时间 |
DECIMAL | NUMBER | NUMERIC | NUMERIC | NUMBER | NUMERIC | NUMBER | NUMBER | double | java.lang.Double | 小数 |
INT | INTEGER | INT | INT4 | INT | INT4 | INTEGER | INT | integer | java.lang.Integer | 整数 |
LONGBLOB | BLOB | IMAGE | BYTEA | BLOB | BYTEA | BLOB | BLOB | string | java.lang.String | 二进制对象,原则不允许 |
LONGTEXT | CLOB | NTEXT | TEXT | CLOB | TEXT | CLOB | CLOB | string | java.lang.String | 大文本 |
SMALLINT | NUMBER(4) | SMALLINT | INT2 | SMALLINT | INT2 | SMALLINT | SMALLINT | integer | java.lang.Integer | 数字枚举 |
VARCHAR | VARCHAR2 | NVARCHAR | VARCHAR | VARCHAR | VARCHAR | VARCHAR | VARCHAR | string | java.lang.String | 文本 |
其它数据库规范,详见开放平台>快速开始>开发规范>数据库部分。
← 后端组件 SpringBean注入规则 →