现在的位置: 首页 > 综合 > 正文

关于Oracle数据库的一点小结

2013年11月04日 ⁄ 综合 ⁄ 共 8267字 ⁄ 字号 评论关闭

  随着信息话的深入,无论是企业还是政府部门都面临一个大数据量的问题。因此对数据的优化就显得尤为重要,这里也重点小结一下数据库的插入和删除的批量操作。对数据的修改一般都是针对一行记录进行的,涉及到大数据量相对比较少。


1、针对删除的批量操作:

  基本思路是构造:delete from 表名 where 字段 in (?, ?, ?)

  以批量删除物料为例的java实现:

  数据访问层代码:这里传入了Connection对象,目的是为了再业务逻辑层处理多个逻辑的时候,保证事务性。因为Oracle默认的是一次提交就执行一次事务。

	public void delItem(Connection conn, String[] itemNos) {
		
		//构造:?,?,?
		StringBuffer sbString =new StringBuffer();
		for (int i=0;i<itemNos.length;i++){
			sbString .append("?");
			if(i<itemNos.length-1){
					sbString.append(",");				
			}
								
		}
		
		String sql= "delete from t_items where item_no in ( "+ sbString+" ) ";
				
		PreparedStatement pstmt = null;
		try{
			pstmt = conn.prepareStatement(sql);
			//赋值
			for(int i =0;i<itemNos.length;i++){
				pstmt.setString(i+1, itemNos[i]);				
			}
			
			pstmt.executeUpdate();
			
			
		}catch(SQLException e){
			e.getStackTrace();
			throw new ApplicationException("删除物料失败!");
			
		}finally{			
			DbUtil.close(pstmt);			
		}		
			
		
	}


业务逻辑层调用删除的方法:在这里完成事务的管理:开启、提交、回滚和关闭。

	public void delItem(String[] itemNos) {
		Connection conn = null;
		try {
			conn = DbUtil.getConnection();
			itemDao.delItem(conn, itemNos);
		}catch(ApplicationException e) {
			throw e;
		}finally {
			DbUtil.close(conn);
		}	
	}


2、关于事务:

  在程序中完成一项功能,往往会需要跨域多个表进行操作,因此正确的保证事务原子性是事务的一个核心之一(另一个重点是事务的隔离级别、事务锁)。

方法一:B层控制事务,将数据库连接传递给D层的方法。

  在上面介绍某企业的drp系统的删除物料的功能实现上就已经用到了一种事务:事务的控制在B层,数据库的连接传递给D层的方法,这样就保证了事务的原子性。

方法二:手动控制事务,重载事务的开启、提交、回滚和重置,这样就取消了默认的自动提交。

  因为会有很多地方用到事务,所以,完全应该提取出到数据库访问的工具类(DbUtil)中:

	/**
	 * 将事务设置成手动提交
	 * @param conn
	 */
	public static void beginTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (conn.getAutoCommit()) {
					conn.setAutoCommit(false); //手动提交
				}
			}
		}catch(SQLException e) {}
	}
	
	/**
	 * 提交事务
	 * @param conn
	 */
	public static void commitTransaction(Connection conn) {
		try {
			if (conn != null) {
				//如果不是自动处理级别,就说明是手动的提交,所以,就要显示执行提交
				if (!conn.getAutoCommit()) {
					conn.commit();
				}
			}
		}catch(SQLException e) {}
	}
	
	/**
	 * 回滚事务
	 * @param conn
	 */
	public static void rollbackTransaction(Connection conn) {
		try {
			if (conn != null) {
				//如果不是自动提交,就说明是手动的回滚,所以,就要显示执行回滚
				if (!conn.getAutoCommit()) {
					conn.rollback();
				}
			}
		}catch(SQLException e) {}
	}
	
	/**
	 * 重置事务,如果是自动的,就修改回手动;如果是手动的,改回到原来的自动状态,这里相当于一个开关,把状态恢复到之前的状态。
	 * @param conn
	 */
	public static void resetConnection(Connection conn) {
		try {
			if (conn != null) {
				if (conn.getAutoCommit()) {
					conn.setAutoCommit(false);
				}else {
					conn.setAutoCommit(true);
				}
			}
		}catch(SQLException e) {}
	}


在添加分销商节点中事务:

  业务介绍:分销商和销售终端采用树形结构展示。选中一个分销商,添加子分销商或者为其添加销售终端。如果原来的分销商是叶子节点,那么添加了子分销商或者终端后,就应该修改为非叶子节点。

  这里涉及到两个操作,一个是向数据库中添加一条新记录,还要查询原来的节点是否是叶子节点,查询出后,还有可能需要修改是否为叶子字段。所以,这里应该保证这一系列为一个原子操作。

	/**
	 * 添加分销商或区域
	 * @param clientOrRegion
	 */
	public void addClientOrRegion(Client clientOrRegion) {
		StringBuffer sbSql = new StringBuffer();
		sbSql.append("insert into t_client ( ")
				   .append("id, pid, client_level_id, ") 
				   .append("name, client_id, bank_acct_no, ") 
				   .append("contact_tel, address, zip_code, ") 
				   .append("is_leaf, is_client) ") 
				.append("values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ");
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			conn = DbUtil.getConnection();
			pstmt = conn.prepareStatement(sbSql.toString());
			//手动控制事务提交
			DbUtil.beginTransaction(conn);
			//取得序号
			pstmt.setInt(1, IdGenerator.generate("t_client"));
			pstmt.setInt(2, clientOrRegion.getPid());
			pstmt.setString(3, clientOrRegion.getClientLevel().getId());
			pstmt.setString(4, clientOrRegion.getName());
			pstmt.setString(5, clientOrRegion.getClientId());
			pstmt.setString(6, clientOrRegion.getBankAcctNo());
			pstmt.setString(7, clientOrRegion.getContactTel());
			pstmt.setString(8, clientOrRegion.getAddress());
			pstmt.setString(9, clientOrRegion.getZipCode());
			pstmt.setString(10, clientOrRegion.getIsLeaf());
			pstmt.setString(11, clientOrRegion.getIsClient());

			//如果事务是自动提交的,不论后面的修改为非叶子的方法,是否能成功执行,这里会提交到数据库,所以,在这个方法中,要将事务修改为手动提交
			pstmt.executeUpdate();
			
			Client parentClientOrRegion = findClientOrRegionById(clientOrRegion.getPid());
			//如果为叶子
			if (Constants.YES.equals(parentClientOrRegion.getIsLeaf())) {
				//修改为非叶子
				modifyIsLeafField(conn, clientOrRegion.getPid(), Constants.NO);
			}
			//提交事务
			DbUtil.commitTransaction(conn);
		}catch(Exception e) {
			e.printStackTrace();
			//回滚事务
			DbUtil.rollbackTransaction(conn);
		}finally {
			DbUtil.close(pstmt);
			DbUtil.resetConnection(conn);
			DbUtil.close(conn);
		}
	}

3、批量添加:

  批量添加跟批量删除是有区别的。在批量删除的时候,实质是执行一条sql语句,只不过是将要删除的语句添加到了in后的条件里,还是执行了一次数据库的连接;在批量添加中,是需要执行多条sql语句,在批量删除中使用StringBuffer构造条件语句,使用循环给占位符赋值。但在批量添加中就需要使用到PreparedStatement
addBatch方法,将一组对象添加到PreparedStatement对象的批处理命令中,对应的要使用到executeBatch方法批量执行。这里也要用的事务的手动提交。

 
StringBuffer:线程同步,线程安全,但是效率不高;

 
StringBuilder:没有线程同步,线程不安全,但是效率高。

	Connection conn= this.getSession().connection(); 
	//自动提交事物设置为false 不自动提交
	conn.setAutoCommit(false);
	String sql = "insert into test(username,userpwd) values(?,?)";
	//创建PreparedStatement 对象
	PreparedStatement pstmt = conn.prepareStatement(sql);
	for (int index = 0; index < number; index++) {
		   pstmt.setString(1,"username");
		   pstmt.setString(2,"userPwd");
		   // 将一组对象添加到prepareStatement对象的批处理密令中
		   pstmt.addBatch();
		   //每5000条进行事物提交
		   if (index%5000==0) {
				   //执行prepareStatement对象中所有的sql语句
				   pstmt.executeBatch(); 
				   //事物提交
				   conn.commit();
				   //数据库与缓存同步
				   this.getSession().flush();
				   //清空缓存
				    this.getSession().clear();
				   //如果连接关闭了 就在创建一个 为什么要这样 原因是 conn.commit()后可能conn被关闭
				   if (null==conn) { 
					   conn = this.getSession().connection();
				    }
			   	}
	   }
	   //执行与5000去摸不是0的数据
	   pstmt.executeBatch();
	   //事物提交
	   conn.commit();
	   //释放连接
	   conn.close();
	   //释放资源
	   pstmt.close();


4、数据库的分页查询:

 
Oracle实现分页查询有三种方法,效率最高的是使用rowid;最慢的是使用分析函数row_number();最常用的也是最容易理解的效率介于两者之间,对于一般的数据量来说,使用最常用的就足够了。不过这里也就物料查询,列出三种哦方法的实现,并做简单的说明。

1.最常用的:

select * from(select t1.*, rownum rn
  from (select *  from t_items  order by item_no ) t1 
 where rownum  < 10 )
 where rn  >= 0;

如果,需要某些字段,需要修改最内层的查询,排序规则也是由最内层决定的。

查询出的rn是Oracle自动分配的,是一个行号,且rownum必须只能使用一次。

上面的语句修改成这样可以执行:

select * from(select t1.*, rownum rn
  from (select *  from t_items  order by item_no ) t1 
 where rownum  < 10 )
 where rownum  >= 0;


如果修改成这样就会提示出错:

select * from(select t1.*, rownum rn
  from (select *  from t_items  order by item_no ) t1 
 where rn  < 10 )
 where rownum  >= 0;

这样是不会识别rn的。


2.效率最高的:

select *
  from t_items
 where rowid in (select item_no
                   from (select rownum rn, item_no
                           from (select rowid item_no
                                   from t_items
                                  order by item_no desc)
                          where rownum < 10)
                  where rn > 0)
 order by item_no desc;


如果,我们只需要查询某些字段,那么需要将最外面的*修改即可;中间层的字段,需要在它内层的查询中包含。


3.分析函数:

select *
  from (select t.*, row_number() over(order by item_no desc) rk
          from t_items t)
 where rk < 10
   and rk > 0;

查询字段也是由最内层决定的,也就是外层的查询要依赖内层查询的字段。


下面是java中,是最常用的分页方法根据查询条件查询分页查询的sql语句构造:

		sbSql.append(" select * ")
			.append(" from (")
				.append(" select i.*, rownum rn from (")
				.append(" select a.item_no, a.item_name, a.spec, a.pattern, a.item_category_id, ")
				.append(" b.name as item_category_name, a.item_unit_id, c.name as item_unit_name , a.file_name ")
				.append(" from t_items a, t_data_dict b, t_data_dict c ")
				.append(" where a.item_category_id=b.id and a.item_unit_id=c.id  ");
				if (condation != null && !"".equals(condation)) {
					sbSql.append(" and (a.item_no like '" + condation + "%' or a.item_name like '" + condation + "%') ");
				}
				sbSql.append(" order by a.item_no")
				.append(") i where rownum<=? ")
				.append(") ")
				.append("where rn >? ");
		System.out.println("sql=" + sbSql.toString());


5、数据库锁:for update语句等同于java的synchronized。两者的差别一个是利用数据库锁一个是使用java的锁。使用java锁更容易控制锁的粒度,可以锁某代码段,也可以对某个类上锁,如果是针对静态变量的锁,其实质也是对静态类的锁。

  用一个手动维护的自增id来说明。因为维护这个id需要所有用户的同步,所以,这里一定是一个静态的线程安全的。

使用数据库的for update悲观锁实现:

        /**
	 * 根据表名生成该表的序列
	 * @param tableName
	 * @return 返回生成的序列
	 */
	public static int generate(String tableName) {
		//使用数据库的悲观锁for update
		String sql = "select value from t_table_id where table_name=? for update";
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		int value = 0;
		try {
			conn = DbUtil.getConnection();
			//开始事务
			DbUtil.beginTransaction(conn);
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, tableName);
			rs = pstmt.executeQuery();
			if (!rs.next()) {
				throw new RuntimeException();
			}
			value = rs.getInt("value");
			//自加
			value++; 
			modifyValueField(conn, tableName, value);
			//提交事务
			DbUtil.commitTransaction(conn);
		}catch(Exception e) {
			e.printStackTrace();
			//回滚事务
			DbUtil.rollbackTransaction(conn);
			throw new RuntimeException();
		}finally {
			DbUtil.close(rs);
			DbUtil.close(pstmt);
			//重置Connection的状态
			DbUtil.resetConnection(conn);
			DbUtil.close(conn);
		}
		return value;
	}


使用java的锁实现:

	public static synchronized int generate(String tableName) {	
		String sql = "select value from t_table_id where table_name=? ";
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		int value = 0;
		try {
			conn = DbUtil.getConnection();
			//开始事务
			DbUtil.beginTransaction(conn);
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, tableName);
			rs = pstmt.executeQuery();
			if (!rs.next()) {
				throw new RuntimeException();
			}
			value = rs.getInt("value");
			//自加
			value++; 
			modifyValueField(conn, tableName, value);
			//提交事务
			DbUtil.commitTransaction(conn);
		}catch(Exception e) {
			e.printStackTrace();
			//回滚事务
			DbUtil.rollbackTransaction(conn);
			throw new RuntimeException();
		}finally {
			DbUtil.close(rs);
			DbUtil.close(pstmt);
			//重置Connection的状态
			DbUtil.resetConnection(conn);
			DbUtil.close(conn);
		}
		return value;
	}

  除了在方法上使用锁,还可以在某些代码段上使用锁,这样可以更好地控制,锁的粒度,提高系统性能。使用synchronized(this) {}将代码段锁住。

  静态方法加锁是给类加的,成员方法加的锁是对象的,因为只有实例化后才可以访问方法。

抱歉!评论已关闭.