一、Mybatis关联查询映射
这里采用一个案例来进行分析:(下面的案例都是采用Mapper接口编程的方式来实现)
数据库准备:在数据库中创建一个Mybatis数据库,并在中创建user(用户)、orders(订单)、orderdetail(订单详情)、items(商品)表。
各表的设计:
user表:
orders表:
orderdetail表:
items表:
表与表的关系:
user/orders表:
user——>orders:一个用户可以有多个订单;一对多
orders——>user:一个订单只能由一个用户创建;一对一
orders/orderdetail表:
orders——>orderdetail:一个订单有多个订单详情,因为一个订单可能包含多个商品;一对多
orderdetail——>orders:一个订单详情只能包含在一个订单当中;一对一
orderdetail/items表:
orderdetail——>items:一个订单明细只能对应一个商品;一对一
items——>orderdetail:一个商品可以有多个订单明细;一对多。
为每个表创建一个javaBean对象:
user表对应的User类:
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; //省略getter和setter方法 }
orders表对应的Orders类:
public class Orders { private Integer id; private Integer user_id; private String number; private Date createtime; private String note; //省略getter和setter方法 }
orderdetail表对应的Orderdetail类:
public class Orderdetail { private Integer id; private Integer ordersId; private Integer itemsId; private Integer itemsNum; //省略getter和setter方法 }
items表对应的Items类:
public class Items { private Integer id; private String name; private float price; private String text; private String pic; private Date createtime; //省略getter和setter方法 }
1.1.一对一查询
需求分析:
查询订单信息,关联查询用户信息。
sql语句:
确定查询主表:订单表
确定查询从表:用户表
SELECT
orders.`id`,
orders.`user_id` userId,
orders.`number`,
user.`username`,
user.`sex`
FROM
orders,
USER
WHERE orders.`user_id` = user.`id`
方式一:采用resultType结果集映射:
由于resultType结果集映射时把查询出来的结果集中的每一列和javaBean对象中每个属性进行一一对应。然而在我们创建的javaBean中Orders表并不满足查询结果集的要求,因此这里就对Orders类进行扩展。
扩展javaBean:OrdersExt
public class OrdersExt extends Orders{ private String username; private String sex; //省略getter和setter方法 }
编写Mapper接口:
public interface OrdersMapper { public List<OrdersExt> findOrdersAndUser(); }
编写OrdersMapper.xml映射文件:
<?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.lc.mybatis.mapper.OrdersMapper"> <!-- 一对一resultType结果映射 --> <select id="findOrdersAndUser" resultType="ordersExt"> SELECT orders.`id`, orders.`user_id` userId, orders.`number`, user.`username`, user.`sex` FROM orders, USER WHERE orders.`user_id` = user.`id` </select> </mapper>
把OrdersMapper.xml映射文件加载到全局配置文件中SqlMapConfig.xml文件中:
我这里使用的是加载包下的所有映射文件的方式来加载映射文件;
<!-- 加载映射文件 --> <mappers> <!-- 注册指定包下的所有映射文件 --> <package name="com.lc.mybatis.mapper"/> </mappers>
测试类:
public class OrdersMapperTest { private SqlSessionFactory sqlSessionFactory ; @Before public void setUp() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOerdersAndUser() { SqlSession sqlSession = sqlSessionFactory.openSession(); //获取UserMapper的代理类 OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List<OrdersExt> lists = ordersMapper.findOrdersAndUser(); System.out.println(lists); sqlSession.close(); } }
方式二:采用resultMap结果集映射
使用resultMap对结果集映射,需要先声明resultMap,因为resultMap是对查询出来的结果集中的每一列进行手动指定映射到javaBean对象中哪个属性上。
修改OrdersExt类:
public class OrdersExt extends Orders{ private User user; //省略getter和setter方法 }
修该OrdersMapper.xml映射文件:
其中映射文件中的:<association property="指定关联关系映射到OrdersExt的哪个属性上" javaType="指定该属性的java类型">;该标签是一对一所使用的关联标签。
<?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.lc.mybatis.mapper.OrdersMapper"> <!-- 一对一resultMap结果映射 --> <!-- 声明resultMap --> <resultMap type="ordersExt" id="findOrdersAndUserRstMap"> <!-- 订单信息 --> <id column="id" property="id"/> <result column="userId" property="userId"/> <result column="number" property="number"/> <!-- 用户信息(一对一) --> <!--association标签:一对一映射 --> <!-- property属性:指定关联信息映射到OrdersExt的哪个属性上 --> <!-- javaType属性:指定关联属性的java类型 --> <association property="user" javaType="user"> <id column="userId" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> </association> </resultMap> <select id="findOrdersAndUserRstMap" resultMap="findOrdersAndUserRstMap"> SELECT orders.`id`, orders.`user_id`userId, orders.`number`, user.`username`, user.`sex` FROM orders, USER WHERE orders.`user_id` = user.`id` </select> </mapper>
修改Mapper接口:
public interface OrdersMapper { public List<OrdersExt> findOrdersAndUserRstMap(); }
测试类:
public class OrdersMapperTest { private SqlSessionFactory sqlSessionFactory ; @Before public void setUp() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersAndUserRstMap() { SqlSession sqlSession = sqlSessionFactory.openSession(); //获取UserMapper的代理类 OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List<OrdersExt> lists = ordersMapper.findOrdersAndUserRstMap(); sqlSession.close(); } }
1.2.一对多关联查询
需求分析:
查询订单信息及订单明细信息。
sql语句:
查询主表:订单表
查询从表:用户表和订单详情表
SELECT
orders.`id`,
orders.`user_id`,
orders.`number`,
user.`username`,
user.`sex`,
orderdetail.`id` detailId,
orderdetail.`items_id`,
orderdetail.`items_num`
FROM
orders,
USER,
orderdetail
WHERE orders.`user_id` = user.`id`
AND orders.`id` = orderdetail.`orders_id`
编写javaBean:
这里查询信息有订单详情和用户信息,因此,Orders类不能满足我们查询是映射的需求,因此,这里同样采用对Orders类进行扩展:
public class OrdersExt extends Orders{ private User user; private List<Orderdetail> orderdetailLists; //省略getter和setter方法 }
编写Mapper接口:
public interface OrdersMapper { public List<OrdersExt> findOrdersAndOrderdetail(); }
编写OrderMapper.xml映射文件:
在映射文件中使用了<collection property="指定关联关信息映射到OrdersExt的哪个属性上" ofType="集合中元素的java类型"> 标签;该标签时一对多关联映射所使用的,把查询的结果集中有多个结果放在集合中去。
<?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.lc.mybatis.mapper.OrdersMapper"> <!-- 声明resultMap --> <resultMap type="ordersExt" id="ordersAndOrderdetail"> <!-- 订单信息 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <!-- 用户信息(一对一) --> <association property="user" javaType="user"> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> </association> <!-- 订单详情信息(一对多) --> <!-- collection标签:由于一对多映射 --> <!-- property属性:指关联信息映射到OrdeersExt的哪个属性上 --> <!-- ofType属性: 集合中java类型--> <collection property="orderdetailLists" ofType="com.lc.mybatis.po.Orderdetail"> <id column="detailId" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> </collection> </resultMap> <!-- 查询订单信息:主信息为订单信息,从信息为用户信息和订单详情 --> <select id="findOrdersAndOrderdetail" resultMap="ordersAndOrderdetail"> SELECT orders.`id`, orders.`user_id`, orders.`number`, user.`username`, user.`sex`, orderdetail.`id` detailId, orderdetail.`items_id`, orderdetail.`items_num` FROM orders, USER, orderdetail WHERE orders.`user_id` = user.`id` AND orders.`id` = orderdetail.`orders_id` </select> </mapper>
加载OrderMapper.xml映射文件:
<!-- 加载映射文件 --> <mappers> <!-- 使用相对于类路径的资源 --> <!-- <mapper resource="com/lc/mybatis/mapper/UserMapper.xml"/> --> <!-- 注册指定包下的所有映射文件 --> <package name="com.lc.mybatis.mapper"/> </mappers>
编写测试类:
private SqlSessionFactory sqlSessionFactory ; @Before public void setUp() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersAndOrderdetail() { SqlSession sqlSession = sqlSessionFactory.openSession(); //获取UserMapper的代理类 OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List<OrdersExt> lists = ordersMapper.findOrdersAndOrderdetail(); sqlSession.close(); } }
1.3.多对多关联查询
其实多对多就是特殊的一对一的关联关系。
需求分析:
查询用户信息及用户购买的商品信息。
sql语句:
主表:user表
从表:orders、orderdetail、items
SELECT
orders.`id`,
orders.`user_id`,
orders.`number`,
user.`username`,
user.`sex`,
orderdetail.`id` detailId,
orderdetail.`items_id`,
orderdetail.`items_num`,
items.`name`,
items.`price`
FROM
orders,
USER,
orderdetail,
items
WHERE orders.`user_id` = user.`id`
AND orders.`id` = orderdetail.`orders_id`
AND orderdetail.`items_id` = items.`id`
修改user表对应的User类:
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Orders> ordersLists;
修改orders表对应的Orders类:
public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private List<Orderdetail> orderdetailList;
修改orderdetail表对应的Orderdetail类;
public class Orderdetail { private Integer id; private Integer ordersId; private Integer itemsId; private Integer itemsNum; private Items items;
修改items表对应的iItems类:
public class Items { private Integer id; private String name; private float price; private String text; private String pic; private Date createtime;
编写Mapper接口:
public interface UserMapper { public List<User> findUserAndItems(); }
编写UserMapper.xml映射文件:
查询用户信息及其关联的商品信息是先查用户信息及关联到的订单信息再到订单详情信息最后到商品信息的一个映射过程,所以在映射文件中要注意各层级的映射关系以及映射标签的嵌套。
<?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.lc.mybatis.mapper.UserMapper"> <!-- 多对多:查询用户购买商品的详细信息 --> <resultMap type="user" id="userAndItems"> <!-- 用户信息 --> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <!-- 订单信息(一对多) --> <collection property="ordersLists" ofType="orders"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <!-- 订单详情信息(一对多) --> <collection property="orderdetailList" ofType="orderdetail"> <id column="detailId" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <!-- items信息:(一对多) --> <association property="items" javaType="items"> <id column="items_id" property="id"/> <result column="name" property="name"/> <result column="price" property="price"/> </association> </collection> </collection> </resultMap> <select id="findUserAndItems" resultMap="userAndItems"> SELECT orders.`id`, orders.`user_id`, orders.`number`, user.`username`, user.`sex`, orderdetail.`id` detailId, orderdetail.`items_id`, orderdetail.`items_num`, items.`name`, items.`price` FROM orders, USER, orderdetail, items WHERE orders.`user_id` = user.`id` AND orders.`id` = orderdetail.`orders_id` AND orderdetail.`items_id` = items.`id` </select> </mapper>
加载UserMapper.xml映射文件:
<!-- 加载映射文件 --> <mappers> <!-- 注册指定包下的所有映射文件 --> <package name="com.lc.mybatis.mapper"/> </mappers>
编写测试类:
public class UserMapperTest { private SqlSessionFactory sqlSessionFactory ; @Before public void setUp() throws IOException{ InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersAndOrderdetail() { SqlSession sqlSession = sqlSessionFactory.openSession(); //获取UserMapper的代理类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> lists = userMapper.findUserAndItems(); sqlSession.close(); } }
1.4.对关联查询做出总结
关联查询,也就是多表查询,查询的结果集也不是一个表所对应的JavaBean对象所能进行直接映射的。因此,我们在进行关联查询要进行合理的JavaBean对象处理和扩展,保证查询出来的结果集都有所对应的JavaBean属性和之对应。这样就能保证查询出来的结果正确无误。
在关联查询中我们常用的标签有:association和collection标签。association标签是一对一关联映射所需要的标签。collection标签是一对多所需要的标签。在Mybatis中,可以理解为多对一也是特殊的一对一(如同:多个员工对应一个部门;但是也可以理解为一个员工对应一个部门,只不过有多个员工而已;可以理解为:在一个Employee对象中有一个department属性;同时又多个Employee对象,每个对象中的department对应同一个部门);多对多是特殊的一对多。
在结果集映射中,我们用的结果集映射总共有两种,分别是:resultType和resultMap;那在关联映射的时候,我们该如何选择使用哪种结果映射方式呢?其实只需要理解两种映射的不同和映射原理。resultType映射时把查询出来的结果集和对应的JavaBean属性进行一一对应。因此,在采用resultType映射,需要映射结果集的javaBean中的所有属性都是与查询结果集进行相互对应的(属性不能进行嵌套)。而使用resultMap结果集映射,则需要先声明resultMap,后使用。先声明resultMap就是制定查询出来的结果集中的列数和javaBean对象中的哪些属性进行关联映射(属性可以嵌套)。
二、Mybatis的延迟加载
在mybatis中只有resultMap标签的association标签和collection标签具有延迟加载的功能。
延迟加载的意思是在进行关联查询时,利用延迟加载,先加载主信息,需要关联信息时,再去按需加载关联信息。这样就会大大的提高数据库的性能,查询单表要比查询多表速度要快的多。
设置延迟加载(打开延迟加载):
Mybatis默认是不开启延迟加载的,需要我们去配置打开延迟加载。需要在全局配置文件中SqlMapConfig.xml中设置lazyLoadingEnabled、aggressiveLazyLoading
开启延迟加载:
<!-- 设置延迟加载 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
上面已经在全局配置文件中打开了延迟加载,下面我们就通过一个案例来测试是否已经可以使用延迟加载了:
需求分析:
查询订单信息及关联的用户信息。
编码实现:
编写Javabean:
public class OrdersExt extends Orders{ private User user; //省略getter和setter方法 }
编写Mapper接口:
public interface OrdersMapper { public List<OrdersExt> findOrdersAndUserLazyLoading(); }
编写OrdersMapper.xml配置文件:
为了使用懒加载,把刚才的需求分解成,首先查询订单信息,然后再按需根据id查询用户信息。
这样就需要配置两个select标签语句;在进行关联查询的时候,association标签找那个的select 是把按需查询用户信息的statement的id,column的作用就是把查询出来的orders信息集合的外键user_id作为findUserById的入参。
<?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.lc.mybatis.mapper.OrdersMapper"> <!-- 需求:查询订单信息(主信息为订单,从信息为用户),并且对关联表进行懒加载 --> <!-- 声明resultMap --> <resultMap type="ordersExt" id="lazyloading"> <!-- 订单信息 --> <id column="id" property="id"/> <result column="user_id" property="user_id"/> <result column="number" property="number"/> <!-- 用户信息(一对一) --> <!-- select属性:指定关联查询statement(即查询用户statement的id),然后将查询的结果封装到property指定的属性中去 --> <!-- column属性:指定查询出来的列column属性的值,作为statement查询语句的入参 --> <association property="user" select="findUserById" column="user_id"></association> </resultMap> <select id="findOrdersAndUserLazyLoading" resultMap="lazyloading"> SELECT * FROM ORDERS </select> <select id="findUserById" parameterType="user" resultType="user"> SELECT * FROM USER WHERE id=#{id} </select> <!-- 在mybatis中,resultMap标签 的association标签和collection标签具有延迟加载的功能。resultType结果映射就没有延迟加载 --> </mapper>
编写测试类:
通过断点方式来测试是否使用了懒加载。在测试语句中得到lists处打断点,通过查询lists中的元素中的user属性是否为空,当执行到获取user对象后,再看lists中元素的user属性是否为空。
public class OrdersMapperTest { private SqlSessionFactory sqlSessionFactory ; @Before public void setUp() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersAndUserLazyLoading() { SqlSession sqlSession = sqlSessionFactory.openSession(); //获取UserMapper的代理类 OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List<OrdersExt> lists = ordersMapper.findOrdersAndUserLazyLoading(); lists.get(0).getUser(); sqlSession.close(); } }
显然通过测试:在第一次获取lists集合,集合中的元素中user属性为空,当获取user后,lists集合中元素的user属性就不为空。
三、Mybatis的逆向工程(会用)
什么是逆向工程:
简单解释:就是通过数据库中的表,自动生成java代码;
下载逆向工程:
逆向工程的下载地址:
https://github.com/mybatis/generator/releases/tag/mybatis-generator-1.3.2
使用逆向工程:
创建项目导入jar包:
创建generator配置文件:
该配置文件放在classpath下:
该配置文件说明看注释;该配置文件可以在下载下来的逆向工程中docs中index.html中找到;
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="testTables" targetRuntime="MyBatis3"> <commentGenerator> <!-- 是否去除自动生成的注释 true:是 : false:否 --> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="mysql"> </jdbcConnection> <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" userId="yycg" password="yycg"> </jdbcConnection> --> <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- targetProject:生成PO类的位置 --> <javaModelGenerator targetPackage="cn.itcast.ssm.po" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> <!-- 从数据库返回的值被清理前后的空格 --> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- targetProject:mapper映射文件生成的位置 --> <sqlMapGenerator targetPackage="cn.itcast.ssm.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- targetPackage:mapper接口生成的位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="cn.itcast.ssm.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <!-- 指定数据库表 --> <table tableName="items"></table> <table tableName="orders"></table> <table tableName="orderdetail"></table> <table tableName="user"></table> </context> </generatorConfiguration>
创建生成表对应类的java类:
public class Generator { public static void main(String[] args) throws Exception{ List<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("config/generatorConfig.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); } }
执行该类中的方法就生成了我们所需要的类: