天天看点

重复插入数据的另类解决思路

        在进行插入数据时,我们会先从数据库查询是否已经拥有该记录,但是最后会发现这个判断没有任何效果,导致这个判断失效的原因有很多,比如事务没有提交,或者多台服务器都执行了相同的代码,或者你的控制器(strut2的Action,springMVC的Controller等)是多实例的!

    该博客旨在解决最后一种问题。

    知识补充:

    1、SpringMvc的Controller是默认是单例的,Struts2的Action是多例的,spring注入默认是单例的。

    2、synchronized(this){

                 同步代码块

           }

          a、与放在方法上修饰一样,都是锁住的对象,也就是说任何线程执行到这里时都需要获得该方法的对象锁,然后才能执行括号里面的代码,称为同步(任何时间都只有一个线程在执行同步代码块)。

          b、如果该代码执行时间特别长,其他线程都在等着代码执行完,就是同步阻塞状态。

          c、this也可以替换为其他的对象,与之类似,其他线程到此时,需要获得该对象的锁,才能执行同步代码块。

          d、特别的,如果方法上有static和synchronized关键字,这时就变成了类锁,要执行同步代码,各个线程之间就需要对类锁竞争,谁有类锁才能够执行。

    3、线程安全指的是多个线程之间的切换不会导致程序执行完后出现多种结果。

        如果你使用Struts2、spring,你会发现这类似于多线程,一条请求进来新建一个Action,然后注入业务实现类。同一时间多个相同请求并发,就会有可能绕过你的重复判断,即同一时间,每个线程都有可能查询不到重复结果。

       解决方法:

       1、通常情况下,我们可以给判断重复的字段上加上联合主键来判断唯一即可,但是很多时候我们不愿意这样做,因为会减少插入的效率。数据库在每次插入时都会遍历数据来判断是否唯一,不唯一就抛出异常,在大数据量大批量插入时,效率急剧下降。

       2、当然你可以直接给你的方法加上synchronized来锁住插入方法的对象,任何人要执行插入的操作方法,就必须得到该方法的对象锁,但是如果方法特别慢,就会同步阻塞。

    此时给出一种思路,用一个线程安全的标识位来实现同步。

    流程:

    1、各个线程将自己需要修改的资源作为标识位,这里我们将可以用来判断重复的字段值作为标志位。

    2、提供一个全局变量存放这些标识位,对这个全局变量的操作必须保证线程安全,即任意时刻都只能有一个线程修改、查找这个全局变量。

    3、每次进行插入操作时,先判断是否全局变量含有自己的标识位。没有,放入自己的标识位;有,说明在此之前已经有其他线程在执行这个方法。

    4、从数据库里查询判断重复。

    5、执行操作方法。

    6、执行完后,释放掉标识位,告诉其他相同标识位的线程“你可以执行了!”,但是具体能不能够执行,也得经过数据库的重复判断。

    请看代码:

public class Cache {
	private static Map<String, String> map = new ConcurrentHashMap<String, String>();

	private Cache() {
	}

	public static Map<String,String> getInstance() {
		return map;
	}
}
           

上面是一个简单的单例对象来作为全局变量,不同的是这个单例是一个Map类型的,我们可以存放多个标识位,这样不同标识位的线程是可以执行的。

private boolean isInCache(String key){
        synchronized (Cache.getInstance())
        {
            if(Cache.getInstance().containsKey(key)){
                return true;
            }else{
            	Cache.getInstance().put(key, key);
            }
        }
       return false;
}
           

以上代码是对全局变量操作的,读者可以看到,synchronized可以保证线程安全,而且是对Map进行put和containsKey操作,效率极快,几乎不会造成同步阻塞。参数key是由你自己定义的,合理使用就可以让有插入相同资源想法的线程们变成同步的。(我这里因为业务需求,orgId和classId两个都相同说明是重复数据, 所以我将两个值拼接起来作为标志位)

if (!isInCache(orgId + classId)){
	if('数据库的重复判断'){
			
	}
}
           

以上代码就是对重复插入的判断,外层判断使线程不重复,内层判断使数据不重复。

synchronized (Cache.getInstance()) {
	Cache.getInstance().remove(orgId + classId);
}
           

最后,代码执行完后,请释放资源,告诉其他相同想法的线程可以进来看一下,但是由于你已经执行了,数据库已经有值了,内层判断不会让它执行的。当然你也可以不释放,最后的结果是Map里面的值越积越多~~

继续阅读