这个问题困扰我有一段时间了,问题应用的场景是:数据库有三张表,两两之间是互相关联的,关联的ID就是各个Table中的主键ID;涉及多表的关系插入,显然在创建表的时候,表的主键ID不能设置成自增(MySql下,auto_increament)键,而且自增主键在并发的场景下,同一时刻如果产生了两个一样的主键ID,是不是insert的时候服务就要崩掉了,就算在事物控制下也会造成用户数据的丢失,非常不友好。怎么办呢?于是乎,我想到了用MySql的序列sequence,我建立了一个全局序列表--sequence,显然三张表的主键ID全部来自于此序列表,不管你怎么来,执行一次select序列表查询,取出的序列值都是不一样的(并发下未做测试,应该能应对),为此,我给出了下面的Mysql.txt文件作为数据演示的脚本:
DROP TABLE player;
DROP TABLE user;
DROP TABLE race;
##创建user
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
`age` int(11) DEFAULT NULL,
`sex` varchar(4) DEFAULT NULL,
`pwd` varchar(50) DEFAULT NULL,
`role` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT
CHARSET=utf8;
##创建player,添加外键key关联
CREATE TABLE `player` (
`pID` INT NOT NULL,
`uID` INT NOT NULL,
`nationality` VARCHAR(100) NULL,
`professional` VARCHAR(100) NULL,
PRIMARY KEY (`uID`, `pID`))
ENGINE = InnoDB
DEFAULT
CHARACTER SET = utf8;
ALTER TABLE player ADD CONSTRAINT uID FOREIGN KEY(uID) REFERENCES `user`(id);
##创建race
CREATE TABLE `race` (
`rID` INT NOT NULL ,
`pID` INT NOT NULL,
`skin` VARCHAR(10) NULL,
PRIMARY KEY (`rID`, `pID`))
ENGINE = InnoDB
DEFAULT
CHARACTER SET = utf8;
##创建序列表
DROP TABLE IF EXISTS sequence;
CREATE TABLE sequence (
name VARCHAR(50) NOT NULL,
current_value INT NOT NULL,
increment INT NOT NULL DEFAULT 1,
PRIMARY KEY (name)
) ENGINE=InnoDB;
##取当前序列中的值
DROP FUNCTION IF EXISTS currval;
DELIMITER $
CREATE FUNCTION currval (seq_name VARCHAR(50))
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE value INTEGER;
SET value = 0;
SELECT current_value INTO value
FROM sequence
WHERE name = seq_name;
RETURN value;
END
$
DELIMITER ;
##取下一个值
DROP FUNCTION IF EXISTS nextval;
DELIMITER $
CREATE FUNCTION nextval (seq_name VARCHAR(50))
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
UPDATE sequence
SET current_value = current_value + increment
WHERE name = seq_name;
RETURN currval(seq_name);
END
$
DELIMITER ;
#更新当前值
DROP FUNCTION IF EXISTS setval;
DELIMITER $
CREATE FUNCTION setval (seq_name VARCHAR(50), value INTEGER)
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
UPDATE sequence
SET current_value = value
WHERE name = seq_name;
RETURN currval(seq_name);
END
$
DELIMITER ;
##创建一个全局对象的序列
INSERT INTO sequence VALUES ('ObjectSeq', 0, 1);
##取一个序列值出来
SELECT NEXTVAL('ObjectSeq');
脚本语句可直接在MySql下批量执行:
有了序列,接着就是在项目中进行演示,到目前为止,我还是没有指出具体的问题究竟是什么,先卖个关子,下面来看一下演示的项目目录树:
我们在SqlMapper中,给出我们的全局序列“ObjectSeq”的查询语句(带返回值的)
为了快速演示,我只给出这个方法在事物控制下,多次调用时所产生的尴尬问题的分析:
A.
B.各位看官注意了,马上就要方法调用了,魔术表演即将开始
(1) API接口调用
(2) Console控制台信息输出
(3) 如果我们不加事物注解呢?
再走一遍,我们发现又不一样了(我们要的就是这个效果):
呵呵呵呵哒,究其原因,还是在事物这块,那就从这里出发,就不信了,mybatis在这个小问题上还能有bug?莫非是......
突然有一天,大神告诉我,应该这样做:
加上一行注解@Options(flushCache=Options.FlushCachePolicy.TRUE)
然后,然后就有然后了(把之前的事物注解再加回来)
再看一下,这个“bug”会不会被完美干掉
Ok,序列值很连续,完美的解决了我心中遗留的问题。 有些人会问我,如果不用@Options注解,能不能解决这个“bug”呢?
我的答案是:能!首先,你得在每一次调用GetObjectSeq方法拿到序列值之后,对这个序列值进行insert操作入库,这样的话,下一次mybaits在遇到select查询序列值的时候,就不会把它当做是一次缓存操作,而是强制刷新缓冲,重新进行select,为什么呢? 在事物下,mybatis会对select语句的结果添加到缓存,换句话说就是:
如果在同一个事物中,多次对同一个查询sql进行执行的话,mybatis只会查询一次数据库,而后几次的返回结果实际上是从缓冲中取的,因此,我们看到,在没有加@Options注解之前,我们查询了三次序列值,返回的结果“惊人的相似”,然而我们加了@Options注解之后,一切又恢复了正常,究其原因,还是我们对mybatis了解的太少,附上一个mybatis3配置+Java API的资料链接,防止日后我们在mybatis中采坑吧:点击打开链接