Mybatis表之间关系有三种:
1、 一对一:人和身份证号是一对一
2、 一对多:一个用户和多个订单是一对多
3、 多对一:多个订单和一个用户是多对一(用户角度)
4、 多对多:老师和学生之间是多对多
特例:如果拿出每一个订单,它都只能属于一个用户(订单角度),所以mybatis中就把多对一看成了一对一。
用户和账户:(一对多+一对一)
1、(用户和账户一对多)一个用户可以有多个账户
2、(账户和用户多对一)一个账户只能属于一个用户(多个账户也可以属于同一个用户)mybatis把多对一看成是一对一
目标:
当我们查询用户时,可以同时得到用户下所包含的账户信息。
当我们查询账户时,可以同时得到账户的所属用户信息。
在对应的四种表关系中,
一对多、多对多:如果关联的对象是多,通常情况下都是采用延迟加载
多对一、一对一:如果关联的对象是一,通常情况下都是采用立即加载,
其实Mybatis没有多对一的概念。
当我们查询账户时,可以同时得到账户的所属用户信息,即(多对一)一对一立即加载步骤:
在查询账户的时候,要不要把关联的用户查出来?
在实际开发中,如果只有账户的信息(id、uid、money),对于使用者来说是不清楚的,因为你只显示uid,使用者不知道uid是谁,通常情况下除了显示账户的信息,还要显示username,从这来看,如果你不显示用户信息,你就不能查出用户名,
立即加载:不管用不用,只要一调用方法,马上发起查询。
故在实际开发中,查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来,
Mybatis一对一实现立即加载
实际上一次查询了两张表,一次把两张表查完了,自然而然没有延迟的概念了,因为是直接把所有数据都查出来。换言之,在实际开发中,在多对一或一对一的情况下,就可以采用此种方式实现功能。
1、 建立两张表:用户表和账户表
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性别', `address` varchar(256) default NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'), (42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'), (45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'), (48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `ID` int(11) NOT NULL COMMENT '编号', `UID` int(11) default NULL COMMENT '用户编号', `MONEY` double default NULL COMMENT '金额', PRIMARY KEY (`ID`), KEY `FK_Reference_8` (`UID`), CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);
让用户表和账户表之间具备一对多的关系,需要在账户表中添加外键
2、创建maven工程,引入依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> </dependencies>
3、 建立两个实体类:
用户实体类
@Data
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
账户实体类
@Data
public class Account implements Serializable{
private Integer id;
private Integer uid;
private Double money;
private User user;
}
让用户和账户的实体类能体现出来一对一的关系,
建立实体类关系的方式,这样我们在封装Account的时候,同时能把User的信息给封装出来。
4、 编写dao接口
public interface IAccountDao { List<Account> findAll(); }
5、编写映射配置文件
账户的配置文件
<?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"> <!--创建映射配置文件,必须与dao接口的包结构相同--> <mapper namespace="com.itheima.dao.IAccountDao"><!--namespace的值是dao接口的全限定类名--> <!-- 定义封装account和user的resultMap --> <resultMap id="accountUserMap" type="Account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容--> <association property="user" javaType="User"> <id property="id" column="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid; </select> </mapper>
Mybatis把返回结果封装成对象。
定义封装account和user的resultMap,由于给account表中的id起了别名aid,故account表中id的列名应该改为aid.
在resultMap中要想封装user的信息,一对一关系的映射用association标签,association标签的property属性值为类中的属性user,此处column=“uid”可以不写,我们写上也没有问题,column属性值表示用户根据id查询时,所需要的参数的值,也就是说你findById,你by的这个id是谁呢?即uid。select * from user where id = #{uid},是通过uid获取user对象的,故column的属性值为uid。注意uid是account表中的外键字段名称,不是你任意取的名字。JavaType属性表示你封装的哪个类的权限定类名com.itheima.domain.User,由于用了别名,故可以直接用user,
注意:当SQL查询出数据后要对结果进行映射,即要定义封装account和user的resultMap
select * from user u,account a where u.id=a.uid
由于user表和account的表的id名称相同,所以第二个出现的会自动起别名。
我们如果不想让它自动起别名,我们可以自己给account表的id起别名。
select u.*,a.id as aid,a.uid,a.money from user u,account a where u.id=a.uid
6、主配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--创建mybatis的主配置文件--> <configuration> <properties resource="jdbcConfig.properties"></properties> <typeAliases> <package name="com.itheima.domain"></package> </typeAliases> <environments default="mysql"> <environment id="mysql"><!--id的值与上面default的值必须相同--> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"><!--配置文件写再dataSource标签下--> <property name="driver" value="${driver}"></property> <property name="url" value="${url}"></property> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> </dataSource> </environment> </environments> <mappers> <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件--> <package name="com.itheima.dao"></package> </mappers> </configuration>
jdbcConfig.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/eesy username=root password=123456
7、 写测试类实现配置:
public class accountTest { private InputStream in ; private SqlSession session; private IAccountDao accountDao; @Before public void init() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 session = factory.openSession(true); //4.使用SqlSession创建Dao接口的代理对象 accountDao = session.getMapper(IAccountDao.class); } @After public void destroy() throws IOException { //session.commit(); //6.释放资源 session.close(); in.close(); } @Test public void testFindAll(){ List<Account> accounts = accountDao.findAll(); for(Account account : accounts){ System.out.println(account); } } }
结果:
当我们查询用户时,可以同时得到用户下所包含的账户信息,即一对多立即加载步骤:
1、 建立两张表:用户表和账户表
2、创建maven工程,引入依赖
3、 建立两个实体类:
账户实体类
@Data public class Account implements Serializable{ private Integer id; private Integer uid; private Double money; }
用户实体类
@Data public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; }
让用户和账户的实体类能体现出来一对多的关系,
4、 编写dao接口
public interface IUserDao {
List<User> findAll();
}
5、编写映射配置文件
用户映射配置文件
<?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"> <!--创建映射配置文件,必须与dao接口的包结构相同--> <mapper namespace="com.itheima.dao.IUserDao"><!--namespace的值是dao接口的全限定类名--> <!-- 定义User的resultMap--> <resultMap id="userAccountMap" type="User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <!-- 配置user对象中accounts集合的映射 --> <collection property="accounts" ofType="Account"> <id column="aid" property="id"></id> <result column="uid" property="uid"></result> <result column="money" property="money"></result> </collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userAccountMap"> select u.*,a.id as aid,a.uid,a.money from user u left outer join account a on u.id = a.uid </select> </mapper>
Mybatis把返回结果封装成对象。
配置user对象中accounts集合的映射,用collection标签,property属性的值为User类中的属性accounts,ofType属性表示集合中元素的全限定类名com.itheima.domain.Account,由于用了别名,故可以直接用account。由于两个表中都有id,故将account表中的id起别名。
6、主配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--创建mybatis的主配置文件-->
<configuration>
<properties resource="jdbcConfig.properties"></properties>
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<environments default="mysql">
<environment id="mysql"><!--id的值与上面default的值必须相同-->
<transactionManager type="JDBC">
</transactionManager>
<dataSource type="POOLED"><!--配置文件写再dataSource标签下-->
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
jdbcConfig.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/eesy
username=root
password=123456
7、 写测试类实现配置:
public class MybatisTest { private InputStream in ; private SqlSession session; private IUserDao userDao; @Before public void init() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 session = factory.openSession(true); //4.使用SqlSession创建Dao接口的代理对象 userDao = session.getMapper(IUserDao.class); } @After public void destroy() throws IOException { //session.commit(); //6.释放资源 session.close(); in.close(); } @Test public void testFindAll() { //5.使用代理对象执行方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); } } }
结果
当我们查询用户时,可以同时得到用户下所包含的账户信息,即一对多延迟加载步骤:
在查询用户的时候,要不要把关联的账户查出来?
我们要做的功能就是查询用户,但是用户中关联了一个账户集合,并且这个用户有100个账户,我们一查用户就会把100个账户同时查出来,这无疑对内存是非常大的开销,而如果我们不用的时候,是完全不应该把这100个账户查出来的,但是这时候有一个新的问题,如果我们要用的话,你没查出来,那我们岂不是用不了了吗?所以这时候我们应该想到一个概念,在查询用户时,用户下的账户信息应该是什么时候使用什么时候查询的,而不应该是只要一查用户就把账户查出来(按需加载)
延迟加载:在真正使用数据时才发起查询,不用的时候不查询,也叫按需加载或懒加载
延迟加载的思想符合查询用户,当有用户要查询用户的账户,或者有用户要查询用户的订单,或者有用户要查询用户的角色,这时候,因为是一个集合,查出来会浪费内存空间,所以应该是延迟加载。
修改如下:
1、在主配置文件中配置参数;
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--创建mybatis的主配置文件--> <configuration> <properties resource="jdbcConfig.properties"></properties> <!--配置延迟加载策略--> <settings> <!--打开延迟加载开关--> <setting name="lazyLoadingEnabled" value="true"/> <!--将积极加载改为消极加载即按需加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings> <typeAliases> <!--<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>--> <package name="com.itheima.domain"></package> </typeAliases> <environments default="mysql"> <environment id="mysql"><!--id的值与上面default的值必须相同--> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"><!--配置文件写再dataSource标签下--> <property name="driver" value="${driver}"></property> <property name="url" value="${url}"></property> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> </dataSource> </environment> </environments> <mappers> <!--<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>--><!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件--> <package name="com.itheima.dao"></package> </mappers> </configuration>
2、改造IUserDao.xml中的findAll方法的sql语句,因为如果用left outer join的话根本不可能实现延迟,因为一次查了两张表。改为一次查一张表
<?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"> <!--创建映射配置文件,必须与dao接口的包结构相同--> <mapper namespace="com.itheima.dao.IUserDao"><!--namespace的值是dao接口的全限定类名--> <resultMap id="userAccountMap" type="User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" column="id" ofType="Account" select="com.itheima.dao.IAccountDao.findAccountByuid"> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"><!--id的值是dao接口方法的名称,id的值与dao接口的全限定类名一一映射--> SELECT * from user </select> </mapper>
修改resultMap配置;在映射配置文件的association标签内将实体类的属性名与数据库的表字段进行映射已经没有意义,因为没有查数据是不可能封装的。使用collection标签的select属性,select属性指定的内容:查询账户的唯一标识,也就是在IAccountDao.xml中能根据用户id查询账户的配置。调用对方映射配置文件中的一个配置来实现查询的功能
注意:collection标签中的column属性值id为User表的主键id,该id作为子查询的参数。
3、在IAccountDao接口中增加findAccountByuid方法
public interface IAccountDao { List<Account> findAccountByuid(Integer uid); }
4、在对方映射配置文件中根据用户id查询账户列表:
<?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"> <!--创建映射配置文件,必须与dao接口的包结构相同--> <mapper namespace="com.itheima.dao.IAccountDao"><!--namespace的值是dao接口的全限定类名--> <select id="findAccountByuid" resultType="Account"> select * from account where uid=#{uid} </select> </mapper>
5、测试
public class MybatisTest { private InputStream in ; private SqlSession session; private IUserDao userDao; @Before public void init() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 session = factory.openSession(true); //4.使用SqlSession创建Dao接口的代理对象 userDao = session.getMapper(IUserDao.class); } @After public void destroy() throws IOException { //session.commit(); //6.释放资源 session.close(); in.close(); } @Test public void testFindAll() { //5.使用代理对象执行方法 List<User> users = userDao.findAll(); } }
结果发现只执行了一个sql语句,实现了延迟的效果
而当要使用用户的账户信息的时候才会查询账户信息,
public class MybatisTest { private InputStream in ; private SqlSession session; private IUserDao userDao; @Before public void init() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 session = factory.openSession(true); //4.使用SqlSession创建Dao接口的代理对象 userDao = session.getMapper(IUserDao.class); } @After public void destroy() throws IOException { //session.commit(); //6.释放资源 session.close(); in.close(); } @Test public void testFindAll() { //5.使用代理对象执行方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); List<Account> accounts = user.getAccounts(); System.out.println(accounts); } } }
一对多延迟加载的思想是在用的时候调用对方映射配置文件中的一个配置来实现查询的功能。