Data Access 之 MyBatis(三) - SQL Mapping XML(Part C)

2022-08-19 17:09:49 浏览数 (1)

一、联合查询

resultMap自定义封装规则

当实体类属性和数据库字段的差异仅仅是 "_" 时,可以通过驼峰转换或者SQL语句中起别名的方式,如果属性和字段不一致,驼峰命名法就无法使用了,而起别名的方式较为繁琐,这时候就可以使用resultMap自定义封装规则

创建一张表t_cat

代码语言:javascript复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_cat
-- ----------------------------
DROP TABLE IF EXISTS `t_cat`;
CREATE TABLE `t_cat` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `cName` varchar(255) DEFAULT NULL,
  `cAge` int(2) DEFAULT NULL,
  `cgender` int(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_cat
-- ----------------------------
BEGIN;
INSERT INTO `t_cat` VALUES (1, '皮皮', 2, 0);
INSERT INTO `t_cat` VALUES (2, '六六', 1, 0);
INSERT INTO `t_cat` VALUES (3, '猪', 1, 0);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

entity包中增加实体类Cat

代码语言:javascript复制
@Data
public class Cat {

    private Integer id;
    private String name;
    private Integer gender;
    private Integer age;
}

dai包中增加CatDao接口

代码语言:javascript复制
public interface CatDao {

    Cat getCatById(Integer id);
}

mappers目录下增加SQL映射文件cat.xml

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.citi.dao.CatDao">

    <!--参数类型不用写-->
    <select id="getCatById" resultType="com.citi.entity.Cat">
        select * from t_cat where id = #{id}
    </select>


</mapper>

将cat.xml注册到MyBatis全局配置文件中

代码语言:javascript复制
<mappers>
    <mapper resource="mappers/employee.xml"/>
    <mapper resource="mappers/cat.xml"/>
</mappers>

生成CatDaoTest测试类

代码语言:javascript复制
public class CatDaoTest {

    SqlSessionFactory sqlSessionFactory = null;
    SqlSession openSession = null;

    @Before
    public void setUp() throws Exception {

        //1、根据全局配置文件创建出一个SqlSessionFactory
        //SqlSessionFactory:是SqlSession工厂,负责创建SqlSession对象;
        //SqlSession:sql会话(代表和数据库的一次会话);
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        openSession = sqlSessionFactory.openSession();

    }

    @Test
    public void getCatById() {
        CatDao catDao = openSession.getMapper(CatDao.class);
        Cat cat = catDao.getCatById(1);
        System.out.println(cat);
    }
}

执行测试方法

除了ID,其余属性都是null,MyBatis默认可以自动封装结果集,但前提是数据库字段和属性值一一对应(不区分大小写),出现这种情况的原因是数据库的字段和实体类属性不一致,导致对象的部分属性赋值失败。如果数据库字段和实体类属性名字的区别只是有无下划线,那么可以在MyBatis全局配置文件中开启驼峰命名法来解决。如果完全不一致则有两种解决办法。

第一种解决办法是在查询SQL中给查询的字段起别名,使之与实体类属性名一致。修改映射文件

代码语言:javascript复制
<select id="getCatById" resultType="com.citi.entity.Cat">
    select id, cname name, cage age, cgender gender from t_cat where id = #{id}
</select>

再次执行测试

第二种方法是使用resultMap标签自定义结果集,实现数据库字段和实体类属性的对应,resultType和resultMap是互斥的,使用了resultMap就不要再使用resultType

代码语言:javascript复制
<select id="getCatById" resultMap="mycat">
    select * from t_cat where id = #{id}
</select>

<resultMap id="mycat" type="com.citi.entity.Cat">
    <!--定义主键映射规则-->
    <id property="id" column="id"></id>
    <!--普通列的映射规则-->
    <result property="gender" column="cgender"></result>
    <result property="age" column="cage"></result>
    <result property="name" column="cname"></result>
</resultMap>
  • type:指定为哪一个实体类自定义封装规则,全类名
  • id:唯一标识,被其他引用
  • column:指定的数据库的字段
  • property:指的是实体类的属性
  • result:标签用来定义非主键列的映射规则

执行测试

联合查询

由于实体类和数据库表是一一对应的,当进行联合查询时没有哪一个实体类可以与联合查询的结果一一对应,这时就可以使用resultMap来封装联合查询的结果

一对一查询

在entity包中新增两个实体类Key、Lock,两者为一对一关系

代码语言:javascript复制
@Data
public class Key {
    private Integer id;
    private String keyName;

    // 能打开的锁
    private Lock lock;
}
代码语言:javascript复制
@Data
public class Lock {

    private Integer id;
    private String lockName;
}

创建数据库表,并通过外键建立关联关系,并插入两条数据

代码语言:javascript复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_key
-- ----------------------------
DROP TABLE IF EXISTS `t_key`;
CREATE TABLE `t_key` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `keyname` varchar(255) DEFAULT NULL,
  `lockid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_key_lock` (`lockid`),
  CONSTRAINT `fk_key_lock` FOREIGN KEY (`lockid`) REFERENCES `t_lock` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_lock
-- ----------------------------
DROP TABLE IF EXISTS `t_lock`;
CREATE TABLE `t_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lockName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

在dao包中新建KeyDao接口

代码语言:javascript复制
public interface KeyDao {
    
    Key getKeyById(Integer id);
}

在mappers中新增key.xml映射文件

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.citi.dao.KeyDao">

    <!--参数类型不用写-->
    <select id="getKeyById" resultMap="mykey">
        SELECT k.id,k.keyname,k.lockid, l.id lid, l.lockname
        FROM t_key k
        LEFT JOIN t_lock l ON k.lockid = l.id
        WHERE k.id = #{id}
    </select>

    <resultMap id="mykey" type="com.citi.entity.Key">
        <id property="id" column="id"></id>
        <result property="keyName" column="keyname"></result>
        <result property="lock.id" column="lid"></result>
        <result property="lock.lockName" column="lockName"></result>
    </resultMap>
</mapper>

SQL查询语句建议使用left Join,不易错。多表联合查询一定要使用resultMap,自定封装规则。这里使用级联属性获取key中所包含的lock的属性

在全局配置文件中注册key.xml

代码语言:javascript复制
<mappers>
    <mapper resource="mappers/employee.xml"/>
    <mapper resource="mappers/cat.xml"/>
    <mapper resource="mappers/key.xml"/>
</mappers>

增加测试类KeyDaoTest

代码语言:javascript复制
public class KeyDaoTest {

    SqlSessionFactory sqlSessionFactory = null;
    SqlSession openSession = null;

    @Before
    public void setUp() throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        openSession = sqlSessionFactory.openSession();
    }


    @Test
    public void getKeyById() {

        KeyDao keyDao = openSession.getMapper(KeyDao.class);
        Key key = keyDao.getKeyById(1);
        System.out.println(key);
    }
}

执行测试

上述方式使用到了级联属性,而MyBatis推荐使用association属性来完成自定义封装规则

修改resultMap

代码语言:javascript复制
<resultMap id="mykey" type="com.citi.entity.Key">
    <id property="id" column="id"></id>
    <result property="keyName" column="keyname"></result>
    <!--<result property="lock.id" column="lid"></result>-->
    <!--<result property="lock.lockName" column="lockName"></result>-->
    <association property="lock" javaType="com.citi.entity.Lock">
        <id property="id" column="id"></id>
        <result property="lockName" column="lockName"></result>
    </association>
</resultMap>
  • association:定义一个复杂类型的关联
  • javaType:指定对象的类型

再次执行测试

一对多查询

在数据库表中新增数据一个3号锁,有多把钥匙可以打开3号锁,也就是说一把锁有多把钥匙,这就形成了一对多的关系

修改Lock实体类,增加钥匙列表的属性

代码语言:javascript复制
@Data
public class Lock {

    private Integer id;
    private String lockName;

    private List<Key> keyList;
}

一对一和一对多的区别在于所站的角度不同,一对多是外键建立在多的一方,多对多时通过中间表建立关系

在dao包中新建LockDao

代码语言:javascript复制
public interface LockDao {

    Lock getLockById(Integer id);

}

新建SQL映射文件lock.xml

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.citi.dao.LockDao">
    
    <select id="getLockById" resultMap="mylock">
        SELECT l.id,l.lockName,k.id kid, k.keyname,k.lockid
            FROM t_lock l
            LEFT JOIN t_key k ON l.id = k.lockid
            WHERE l.id=#{id}
    </select>

    <resultMap id="mylock" type="com.citi.entity.Lock">
        <id property="id" column="id"></id>
        <result property="lockName" column="lockName"></result>
        <collection property="keyList" ofType="com.citi.entity.Key">
            <!--column是起的别名,如果没起别名,就和数据库列字段一致-->
            <id property="id" column="kid"></id>
            <result property="keyName" column="keyName"></result>
        </collection>
    </resultMap>
</mapper>
  • collection:复杂类型的集合
  • ofType:指定集合里面元素的类型

将lock.xml注册到MyBatis全局配置文件中

代码语言:javascript复制
<mappers>
    <mapper resource="mappers/employee.xml"/>
    <mapper resource="mappers/cat.xml"/>
    <mapper resource="mappers/key.xml"/>
    <mapper resource="mappers/lock.xml"/>
</mappers>

新建测试类

代码语言:javascript复制
public class LockDaoTest {

    SqlSessionFactory sqlSessionFactory = null;
    SqlSession openSession = null;

    @Before
    public void setUp() throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        openSession = sqlSessionFactory.openSession();
    }

    @Test
    public void getLockById() {
        LockDao lockDao = openSession.getMapper(LockDao.class);
        Lock lock = lockDao.getLockById(3);
        System.out.println(lock);
        List<Key> keyList = lock.getKeyList();
        for (Key key : keyList) {
            System.out.println("Key:"   key);
        }
    }
}

执行测试

联合查询较为繁琐,使用分步查询也可以完成;先查出Key,在根据Key的信息到t_lock表中查出锁的信息

分步查询 Key -> Lock

在KeyDao和LockDao中分别增加一个查询方法

代码语言:javascript复制
Lock getLockByIdSimplely(Integer id);
代码语言:javascript复制
Key getKeyByIdSimplely(Integer id);

在lock.xml映射文件中增加SQL语句

代码语言:javascript复制
<select id="getLockByIdSimplely" resultType="com.citi.entity.Cat">
    select * from t_lock where id = #{id}
</select>

在key.xml中完成分步查询

代码语言:javascript复制
<select id="getKeyByIdSimplely" resultMap="mykeysimplely">
    select * from t_key where id = #{id}
</select>

<resultMap id="mykeysimplely" type="com.citi.entity.Key">
    <id property="id" column="id"></id>
    <result property="keyName" column="keyname"></result>
    <association property="lock"
                 select="com.citi.dao.LockDao.getLockByIdSimplely"
                 column="lockid">
    </association>
</resultMap>
  • property:指定封装哪个属性
  • select:指定一个查询SQL的唯一标识,MyBatis自动调用指定的SQL将查询的结果结果封装到指定的属性
  • colume:指定将哪一列数据传递过去

在KeyDaoTest中增加getKeyByIdSimplely方法的测试代码

代码语言:javascript复制
@Test
public void getKeyByIdSimplely() {

    KeyDao keyDao = openSession.getMapper(KeyDao.class);
    Key key = keyDao.getKeyByIdSimplely(1);
    System.out.println(key);
}

执行测试

根据控制台打印的信息可以看出分步查询其实就是分别执行了两条SQL,先获取Key,在获取Lock;每次查询Key都要数据库执行两条SQL严重影响性能,而且并不是每次查询Key的时候也需要锁的信息,因此对查询锁信息可以使用按需加载,需要的时候再执行查锁。

在MyBatis全局配置文件中配置开启延迟加载和属性按需加载

代码语言:javascript复制
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="true"/>
</settings>

将打印key的代码注释(不注释会调用Key的toString方法导致懒加载失效),再次执行测试

控制台只会打印出一条SQL语句,也就是后台只执行了一条SQL语句

修改测试代码,获取Lock

代码语言:javascript复制
@Test
public void getKeyByIdSimplely() {

    KeyDao keyDao = openSession.getMapper(KeyDao.class);
    Key key = keyDao.getKeyByIdSimplely(1);
    // System.out.println(key);
    String lockName = key.getLock().getLockName();
    System.out.println(lockName);
}

再次测试

覆盖全局的延迟加载配置,在association标签中增加fetchType="eager",注释掉获取锁的代码,再次执行测试

分步查询 Lock -> Key

分别在LockDao、KeyDao中新增方法

代码语言:javascript复制
Lock getLockByIdStep(Integer id);
代码语言:javascript复制
List<Key> getKeysByLockId(Integer id);

在key.xml中新增查询key的SQL语句

代码语言:javascript复制
<select id="getKeysByLockId" resultType="com.citi.entity.Key">
    select * from t_key where lockid = #{id}
</select>

在lock.xml中新增getLockByIdStep方法的映射

代码语言:javascript复制
<select id="getLockByIdStep" resultMap="mylockstep">
    select * from t_lock where id = #{id}
</select>

<resultMap id="mylockstep" type="com.citi.entity.Lock">
    <id property="id" column="id"></id>
    <result property="lockName" column="lockName"></result>
    <collection property="keyList"
                select="com.citi.dao.KeyDao.getKeysByLockId"
                column="id">
    </collection>
</resultMap>

增加测试方法

代码语言:javascript复制
@Test
public void getLockByIdStep() {
    LockDao lockDao = openSession.getMapper(LockDao.class);
    Lock lock = lockDao.getLockByIdStep(3);
    System.out.println(lock);
    List<Key> keyList = lock.getKeyList();
    for (Key key : keyList) {
        System.out.println("Key:"   key);
    }
}

执行测试

reslutMap标签中的 column属性是用来传递select属性指定的查询方法的参数,如果该查询方法有多个参数则可以使用{key1=value1,key2=value2}的方式来传递数据。

实际应用中更推荐使用连接查询的方式而非分步查询,相比较会减少对数据库的压力

0 人点赞