多对多

在数据库设计中,“多对多”关系是指两张表中的记录可以互相关联,多条记录可以关联多条记录。例如,“学生”表和”课程”表之间的关系可能是多对多,因为一个学生可以选修多门课程,而一门课程也可能被多名学生选修。

要实现多对多的关系,通常需要使用一个中间表(关联表)。这个中间表起到桥梁的作用,将两个表的记录通过其主键关联起来。

多对多关系的特点

  • 双向性:A可以关联多个B,同时B也可以关联多个A
  • 需要中间表:为了表示这种关系,通常使用一个中间表来维护关联
  • 灵活性高:多对多关系非常适合用来表示复杂的业务逻辑

查询模型

用户表和角色的关系,一个用户有多个角色,一个角色被多个用户使用。多对多的查询需求是查询用户同时查询出该用户所有的角色。

创建表

wzk_user_role

CREATE TABLE `wzk_user_role` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

wzk_role

CREATE TABLE `wzk_role` (
  `id` int(11) NOT NULL,
  `rolename` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

插入数据

wzk_user_role

INSERT INTO wzk_user_role VALUES(1, 1, 1);
INSERT INTO wzk_user_role VALUES(1, 2, 2);

wzk_role

INSERT INTO wzk_role VALUES(1, "ADMIN");
INSERT INTO wzk_role VALUES(2, "USER");

查询语句

select u.*,r.*,r.id rid from wzk_user u
left join wzk_user_role ur on u.id=ur.user_id
inner join wzk_role r on ur.role_id=r.id;

创建类

WzkUser

package icu.wzk.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WzkUser {
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private List<WzkOrder> orderList;
    private List<WzkRole> roleList;
}

WzkRole

package icu.wzk.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WzkRole {
    private Integer id;
    private String rolename;
}

UserMapper

package icu.wzk.mapper;

import icu.wzk.model.WzkUser;
import java.util.List;

public interface UserMapper {
    List<WzkUser> findAll();
    List<WzkUser> findAllUserAndRole();
}

UserMapper.xml

<resultMap id="userRoleMap" type="icu.wzk.model.WzkUser">
    <result column="id" property="id"></result>
    <result column="username" property="username"></result>
    <result column="password" property="password"></result>
    <result column="birthday" property="birthday"></result>
    <collection property="roleList" ofType="icu.wzk.model.WzkRole">
        <result column="rid" property="id"></result>
        <result column="rolename" property="rolename"></result>
    </collection>
</resultMap>

<select id="findAllUserAndRole" resultMap="userRoleMap">
    SELECT u.*, r.*, r.id rid
    FROM wzk_user u
    LEFT JOIN wzk_user_role ur on u.id=ur.user_id
    INNER JOIN wzk_role r on ur.role_id=r.id;
</select>

编写代码

package icu.wzk;

import icu.wzk.mapper.UserMapper;
import icu.wzk.model.WzkUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class WzkIcu10 {
    public static void main(String[] args) throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<WzkUser> dataList = userMapper.findAllUserAndRole();
        dataList.forEach(System.out::println);
        sqlSession.close();
    }
}

运行结果

WzkUser(id=1, username=wzk, password=icu, birthday=Mon Nov 11 00:00:00 CST 2024, orderList=null, roleList=[WzkRole(id=1, rolename=ADMIN)])
WzkUser(id=2, username=wzk2, password=icu2, birthday=Mon Nov 11 00:00:00 CST 2024, orderList=null, roleList=[WzkRole(id=2, rolename=USER)])

注意事项

  • 索引优化:为中间表的外键字段创建索引,提升查询性能
  • 关系约束:使用外键约束维护数据一致性,避免孤立数据
  • 批量操作:在大规模插入、更新时,尽量使用批量操作,提升效率
  • 软删除:中间表可以设计”软删除”标志位,用于保留历史记录

优化多对多关系模型

优化多对多关系模型需要从多个方面入手:

  • 提升性能:通过索引、分区、批量操作等方式优化查询和更新
  • 保证数据一致性:通过外键约束和事务处理避免数据异常
  • 提高灵活性:通过软删除和日志记录保留历史数据,支持恢复和审计
  • 应对大数据场景:通过分区、分表或分库设计,确保系统扩展性