# 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 文本

其它数据库规范,详见开放平台>快速开始>开发规范>数据库部分。

创建人:het
修改人:het