需求:建立Course表(等)与Teacher表的关联
分析: 当两个实体之间存在着多对多的映射关系时,往往需要一张中间表来存放。但是,多一张表就会多一个实体类、Mapper、Dao等等,就需要更多的代码去维护。因此我们需要尽量减少数据库的复杂度。在这个例子中,我们就可以避免使用中间表,原因在于:
- 老师的数量有限,(和需求方沟通后确定)不会多于30位老师使用本系统。
- 除了Course外还有诸如Project、Module等等实体都要与Teacher表关联,而且关系都是多对多,这样增加的就不是一张中间表而是很多中间表,这势必会造成更大的维护困难。
在这种情况下,可以使用位操作的办法来实现多对多映射,避免中间表。
以Course表为例,用一个Long型的字段来存放teacher:
Integer是4个字节32位,其实已经满足需求,但是保险起见还是使用了Long来存放这个teacher的信息,这样的话老师用户最多可以有64人。
存储的原理是,第n位表示编号为n的老师和这门课的关系,1代表要上这门课,0代表不上。假设已经有4位老师:(忽略0号的管理员)
那么0001就可以表示1号老师上这门课,1010表示2号和4号老师上这门课,etc.
这样一来Long 64位就可以表示64位老师和这门课的关系。
实现: 创建config工具类,里面使用静态方法来实现我们的转换。
1. 把id转换成Long(编码)
Java里需要用到字节数组。创建一个长度为8的字节数组(一个字节8比特,所以一共是64位)byte[] bytes = new byte[8],Java会自动帮我们把所有位初始化为0。如果想要把某个字节的某一位设成1,可以让字节与一个整数做或运算,这个整数由1左位移得到。比如,把byte的(从0开始)第四位设成1,我们需要:
byte |= 1<<4;
1:0000 0001,左位移4位后:0001 0000,然后做或运算即可把byte的第4位标记成1。
用这个原理可以得到一个标识好位数的字节数组,代码:
static byte[] getByteList(Integer[] pos) //把长整型转换成字节数组
{
byte[] bytes = new byte[8]; //64位数拆成8字节的字节数组
for(int p : pos)
{
if(p<1||p>64) continue;
int x=(p-1)/8,y=(p-1)%8;
bytes[x] |= 1 << y; //将字节数组的第x位赋2的y次方
}
return bytes;
}
接下来还要把字节数组转成Long型长整数,以便存放数据库。对于原理部分可以参考https://blog.csdn.net/u013556056/article/details/81019509(转的是Integer,原理都差不多),而对于Long的转换要稍微有点区别,如果直接套用int的方法改成long,会出现这个问题:
当左位移大于32时,IDEA提示左位移32相当于移0,移40相当于移8等等。
这是因为byte类型的移位会被自动强转成int型,int一共只有32位,那么左移超过32位其实就相当于没移位。因此要在long型上移位,只能先转成long型再移位。注意long型在&0xff时也会有变化。代码:(为什么要&0xff原文有,一句话总结就是:byte强转成int或long会补位,但补的都是1不是0,所以要清零)
static long convertByteToLong(byte[] bytes) //把字节数组拼在一起成为一个长整形long
{
long l0 = bytes[0] & 0x00000000000000ffL;
long l1 = ((long)bytes[1]<<8 ) & 0x000000000000ff00L;
long l2 = ((long)bytes[2]<<16) & 0x0000000000ff0000L;
long l3 = ((long)bytes[3]<<24) & 0x00000000ff000000L;
long l4 = ((long)bytes[4]<<32) & 0x000000ff00000000L;
long l5 = ((long)bytes[5]<<40) & 0x0000ff0000000000L;
long l6 = ((long)bytes[6]<<48) & 0x00ff000000000000L;
long l7 = ((long)bytes[7]<<56) & 0xff00000000000000L;
return l0 | l1 | l2 | l3 | l4 | l5 | l6 | l7; //把8个长整型数的各个字节位取出来拼在一起
}
这样就得到了Long型的数据,相当于编码,储存在数据库里。
2. Long转id(解码)
从数据库取出Long型数据还要解码成老师的id,其步骤和上面是相反的。
先要把Long型转成字节数组。强转byte其实就是取出long的前8位。代码:
static byte[] convertLongToByte(long num) //把长整型拆成长度为8的字节数组
{
byte[] bytes = new byte[8];
bytes[0] = (byte)num;
bytes[1] = (byte)(num>>8);
bytes[2] = (byte)(num>>16);
bytes[3] = (byte)(num>>24);
bytes[4] = (byte)(num>>32);
bytes[5] = (byte)(num>>40);
bytes[6] = (byte)(num>>48);
bytes[7] = (byte)(num>>56);
return bytes;
}
得到字节数组,要还原出id的List,只需要for循环遍历就行。代码:
static List<Integer> getIndexList(byte[] bytes) //提取字节数组中标志位1的下标
{
List<Integer> indexList = new ArrayList<>();
for(int i=0;i<8;i++)
{
int b = bytes[i];
for(int j=0;j<8;j++)
{
if((b&0x01)==1) indexList.add(i*8+j+1);
b = b>>1;
}
}
return indexList;
}