Mybatis

这个学习内容是借鉴的,忘记是转载哪里了。做笔记是为了不让我遗忘太快。
里面的过程和代码都已经自己实现过了,同时有一些也加了自己的理解。

文章目录

1.Mybatis的入门

1.mybatis环境搭建的基本架构

第一步:创建maven工程并导入坐标

第二步:创建实体类和dao的接口

第三步:创建Mybatis的主配置文件
SqlMapConifg.xml 名字可以随意,但后缀名要一样

<?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>
    <!-- 配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务的类型-->
            <transactionManager type="jdbc"></transactionManager>
            <!-- 配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!-- 配置链接数据库的四个基本信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/dao/IUserDao.xml"/>
    </mappers>
</configuration>

第四步:创建映射配置文件
IUserDao.xml 名字可以随意,但后缀名要一样

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.dao.IUserDao">
    <!--配置查询所有  其中id不能乱写必须是dao接口中的方法  resultType写的是实体类的全路径-->
    <select id="findAll" resultType="com.dao.IUserDao">
        select * from user
    </select>
</mapper>

环境搭建的注意事项:
第一个: 创建IUserDao.xml 和 IUserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
第二个: 在idea中创建目录的时候,它和包是不一样的
(Package) 在创建时:top.zoick.dao它是三级结构
目录 (Directory) 在创建时:top.zoick.dao是一级目录
第三个: mybatis的映射配置文件位置必须和dao接口的包结构相同
第四个: 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
第五个: 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

2.mybatis基于注解的入门案例

把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句

public interface IUserDao {
    @Select("select * from user")
    List<User> findAll();
}

同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。

<mappers>
    <mapper class="com.dao.IUserDao"/>
</mappers>

明确:
我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。
不管使用XML还是注解配置。

但是Mybatis它是支持写dao实现类的。

3.自定义Mybatis的分析__执行查询所有的分析

Mybatis在使用代理dao的方式实现增删该查时做什么事呢?
只有两件事:
第一:创建代理对象
第二:在代理对象中调用selectList

自定义mybatis能通过入门案例看到类
class    Resources  使用类加载器读取配置文件的类
class    SqlSessionFactoryBuilder  用于创建一个SqlsessionFactory对象
interface  SqlSessionFactory  用于打开一个新的SqlSession
interface  SqlSession  自定义MYbatis中和数据库交互的核心类

在这里插入图片描述

自定义Mybatis开发流程图:

在这里插入图片描述

2.基本的CRUD

1.映射文件里的parameterType

在IUserDao添加saveUser方法,用来保存用户

public interface IUserDao {
    void saveUser(User user);  //保存用户操作
}

在映射文件需要添加以下的saveUser映射代码:

<!-- 保存用户-->
 <insert id="saveUser" parameterType="com.User">   <!--parameterType是参数的类型-->
   insert into user(username,address,sex,birthday) values (#{username},#{address},#{sex},#{birthday})
 </insert>

mybatis中为什么能直接写username,而不用user.呢:
    因为在parameterType中已经提供了属性所属的类, 所以此时不需要写对象名

注意:
能直接写username等,需要使用IDEA自动构造的set和get函数

parameterType是参数的类型,后面是全限定类名

2.测试保存用户saveUser方法

@Test    //测试方法
public void testSave() throws Exception{
	User user = new User();
	user.setUsername("abc");
	user.setAddress("汕头市");
	user.setSex("男");
	user.setBirthday(new Date());

	//1.读取配置文件
	InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
	//2.创建SqlSessionFactory工厂
	SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
	//3.使用工厂生产SqlSession对象
	SqlSession session = factory.openSession();
	//4.使用SqlSession创建Dao接口的代理对象
	IUserDao userDao = session.getMapper(IUserDao.class);
	userDao.saveUser(user);
    
    session.commit();   //需要执行事务提交,不然会回滚事务
	session.close();
	in.close();
}

需要注意的是最后需要执行事务提交

3.对初始化操作,释放资源操作进行封装,如读取配置文件、创建工厂等

private InputStream in;
private SqlSession session;
private IUserDao userDao;

@Before    //用于在测试方法执行之前执行,即在执行加了@Test的测试方法前执行
public void init()throws Exception{    //对初始化进行封装
	//1.读取配置文件
	in = Resources.getResourceAsStream("SqlMapConfig.xml");
	//2.创建SqlSessionFactory工厂
	SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
	//3.使用工厂生产SqlSession对象
	session = factory.openSession();
	 //4.使用SqlSession创建Dao接口的代理对象
	userDao = session.getMapper(IUserDao.class);
}

@After   //用于在测试方法执行之后执行,即在执行加了@Test的测试方法后执行
public void destory()throws Exception{  //对释放资源进行封装
    session.commit();     //对事务进行提交
    session.close();
    in.close();
}

4.对Test,Before,After注解大概认识

@Test 注解:被打上该注解的方法,表示为一个测试方法。
@Before注解:用于在测试方法执行之前执行,即在执行加了@Test的测试方法前执行
@After注解:用于在测试方法执行之后执行,即在执行加了@Test的测试方法后执行

5.对以上封装和测试方法合并

public class text1 {
    private InputStream in;
    private SqlSession session;
    private IUserDao userDao;

    @Before
    public void init()throws Exception{
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.使用工厂生产SqlSession对象
        session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        userDao = session.getMapper(IUserDao.class);
    }

    @After
    public void destory()throws Exception{  //释放资源
        session.commit();
        session.close();
        in.close();
    }
    
    @Test
    public void testSave(){
        User user = new User();
        user.setUsername("abc2345");
        user.setAddress("汕头市");
        user.setSex("男");
        user.setBirthday(new Date());
        userDao.saveUser(user);
    }
}

6.xml的selectKey的使用

由于数据库的id是自增长的,所以插入的数据会自动获得id。selectKey就是来给这个实体类的id加上数据库给的id.

selectKey参数:
keyProperty对应实体类属性名:id,keyColumn对应于数据库:id resultType为数据类型,order为在这条insert sql语句执行前还是执行后做

<!--保存用户-->
    <insert id="saveUser" parameterType="top.zoick.domain.User">
        <!--配置插入操作后,获取插入数据的id-->
            <!--keyProperty对应实体类属性名:id,keyColumn对应于数据库:id resultType为数据类型,order为在这条insert sql语句执行前还是执行后做-->
        <selectKey keyProperty="userId" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into user (username,address,sex,birthday) values (#{userName},#{userAddress},#{userSex},#{userBirthday})
    </insert>
@Test
    public void testSave() {
        User user = new User();
        user.setUsername("gjj67");
        user.setAddress("汕头市");
        user.setSex("男");
        user.setBirthday(new Date());
        System.out.println("保存用户之前"+user);   //id此时为空
        userDao.saveUser(user);
        System.out.println("保存用户之后"+user);   //id被赋上了值
    }

7.OGNL表达式

Object Graphic Navigation Language
对象   图   导航    语言
它是通过对象的取值方法来获取数据。·在写法上把get给省略了
比如:我们获取用户的名称
    类中的写法:user.getUsername,
    OGNL表达式写法:user.username
mybatis中为什么能直接写username,而不用user.呢:
    因为在parameterType中已经提供了属性所属的类, 所以此时不需要写对象名

8.数据库表的元素名与实体类的属性名不对应的解决

1.起别名:

<select id="findAll" resultType="com.User">
        <!--实体类中的属性名和数据库表中元素名字匹配不上时,使用别名即可匹配。id为数据库中表的列名,userId为实体类中属性名-->
        select id as userId,username as userName,address as userAddress ,sex as userSex, birthday as userBirthday from user
    </select>

2.配置查询结果的列名和实体类的属性名的对应关系:

使用resultMap。具体实现如下:

<!--配置查询结果的列名和实体类的属性名的对应关系,id的名可以随意起-->
    <resultMap id="userMap" type="com.User">
        <!--主键字段的对应。 property为实体类的属性名,colum为数据库的属性-->
        <id property="userId" column="id"></id>
        <!--非主键字段的对应-->
        <result property="userName" column="username"></result>
        <result property="userAddress" column="address"></result>
        <result property="userSex" column="sex"></result>
        <result property="userBirthday" column="bithday"></result>
    </resultMap>

再在查询的sql语句的xml中加入配置
  <select id="findAll" resultMap="userMap">
        select * from user
  </select>

9.配置xml中的属性标签

1.使用properties配置数据库连接信息

可以在标签内部配置数据库连接信息,也可以通过外部文件来配置数据库连接信息。

第一种resource属性(常用)

用于指定配置文件的位置,是按照类路径来写的,必须保存与类路径下。

<properties resource="jdbcConfig.properties">
</properties>

第二种url属性(不常用)

URL属性:
URL:Uniform Resource Locator 统一资源定位符 可以唯一标志一个资源的位置
写法必须是
    http://localhost:8080/mybatisserver/demo1Servlet
    协议  主机 端口 URI
  URI:Uniform Resource Identifier 统一资源标识符 是在应用中可以可以唯一标志一个资源的位置
  URL>URI(精准性)

<properties
url="file:///D:/IDEA/localProject/Mybatis_day01/src/main/resources/jdbcConfig.properties">
</properties>

2.使用typeAliases配置别名

<!--使用typeAliases配置别名,他只能配置domain中类的别名-->
    <typeAliases>
        <!--typeAlias用于配置别名,type属性指定的是实体类中的全限定类名。alias属性指定别名,当指定了别名后不在区分大小写-->
        <typeAlias type="com.User" alias="user"></typeAlias>
    </typeAliases>

使用了以上配置别名后,在IUserDao.xml映射文件每条SQL语言的返回类型或参数不需要全限定类名了,只需user。

3.使用package配置别名

<typeAliases>
    <!--用于指定要配置别名的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
    <package name="top.zoick.domain"/>    <!-- name里的内容为举个例子而已 -->
</typeAliases>

其中,配置映射文件位置的中也有package这个标签

<mappers>
<!--     <mapper resource="top/zoick/dao/IUserDao.xml"/>   -->
     <!--package标签是用于指定dao接口所在的包,当指定了之后就不需要再写mapepr以及resource或者class了-->
     <package name="com.dao"/>
</mappers>

4.xml的全部代码:

SqlMapConfig.xml

<?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 属性:(常用)
                用于指定配置文件的位置,是按照类路径来写的,必须存在于类路径下
        URL属性:
            URL:Uniform Resource Locator    统一资源定位符 可以唯一标志一个资源的位置
            写法必须是
                http://localhost:8080/mybatisserver/demo1Servlet
                协议      主机    端口     URI
            URI:Uniform Resource Identifier 统一资源标识符 是在应用中可以可以唯一标志一个资源的位置
            URL>URI(精准性)
    -->
    <properties url="file:///D:/IDEA/localProject/Mybatis_day01/src/main/resources/jdbcConfig.properties">
<!--        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
<!--        <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>-->
<!--        <property name="username" value="root"/>-->
<!--        <property name="password" value="root"/>-->
    </properties>
    
    <!--使用typeAliases配置别名,他只能配置domain中类的别名-->
    <typeAliases>
        <!--typeAlias用于配置别名,type属性指定的是实体类中的全限定类名。alias属性指定别名,当指定了别名后不在区分大小写-->
<!--    <typeAlias type="top.zoick.domain.User" alias="user"></typeAlias>-->

        <!--用于指定要配置别名的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
        <package name="top.zoick.domain"/>
    </typeAliases>
    
    

    <!-- 配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务的类型-->
            <transactionManager type="jdbc"></transactionManager>
            <!-- 配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!-- 配置链接数据库的四个基本信息-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
<!--        <mapper resource="top/zoick/dao/IUserDao.xml"/>-->
        <!--package标签是用于指定dao接口所在的包,当指定了之后就不需要再写mapepr以及resource或者class了-->
        <package name="top.zoick.dao"/>
    </mappers>


</configuration>

jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456

3.连接池、事务控制、动态SQL

1.连接池

在实际开发中都会使用连接池
    因为它可以减少我们获取连接所消耗的时间

mybatis中的连接池:
    mybatis连接池提供了3种配置方式:
    配置的位置:
      主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式。
    type属性的值:
      POOLED  采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
      UNPOOLED 采用传统的获取连接的方式,虽然也实现javax.sql.DataSource接口,但是并没有使用池的思想
      JNDI   采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样的
    注意:如果不是web或者maven的war工程,是不能使用的
      学习时使用的是tomcat服务器,采用的连接池就是dbcp连接池

2.MyBatis中的事务

什么是事务
  事务的四大特性ACID
  不考虑隔离性会产生的3个问题
  解决办法:四种隔离级别

它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚

3.动态SQL

1.if标签的使用

接口中的定义

List<User> findByCondition(User user);

mapper中定义

<select id="findByCondition" resultMap="userMap" parameterType="user">
        select * from user where 1=1
        <if test="userName != null">
            and username = #{userName}
        </if>
        <if test="userSex != null">
            and sex = #{userSex}
        </if>    
</select>

其中: 这条语句中,userName值得是实体类中的属性名, and username = #{userName}这里是将实体类的userName传给数据库的username

2.where和foreach标签的使用

用于多个查询的sql
select * from user where id in(41,42,45)
通过一个类中传入集合的方法
QueryVo类

public class QueryVo {
 	private User user;
    private List<Integer> ids;
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
    
}

接口的定义

 /**
     * 根据Queryvo中提供的id集合,查询用户信息
     * @param vo
     * @return
     */
    List<User> findUserInIds(QueryVo vo);

mapper中定义

<!--根据queryvo中Id集合实现查询用户列表-->
    <select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
        select * from user
        <where>
            <if test="ids != null and ids.size()>0">
                <foreach collection="ids" open="and id in (" close=")" item="uuuuid" separator=",">
                    #{uuuuid}         <!--把集合ids的一个个id放在uuuuid中-->
                </foreach>
            </if>
        </where>
    </select>

注意if标签中的内容都是来源于parameterType参数

测试代码

/**
     * 测试foreach标签的使用
     * @throws IOException
     */
    @Test
    public void testFindInIds () throws Exception {
        QueryVo vo = new QueryVo();
        List<Integer> list = new ArrayList<Integer>();
        list.add(41);
        list.add(42);
        list.add(45);
        vo.setIds(list);
        List<User> users = userDao.findUserInIds(vo);
        for (User user : users) {
            System.out.println(user);
        }
    }

3.抽取重复的sql

mapper定义

<!--抽取重复的sql语句-->
    <sql id="defaultUser">
        select * from user
    </sql>

使用

<include refid="defaultUser"></include>

总使用

<!--抽取重复的sql语句-->
    <sql id="defaultUser">
        select * from user
    </sql>

    <!--配置查询所有  其中id不能乱写必须是dao接口中的方法  resultType写的是实体类的全路径-->
    <select id="findAll" resultMap="userMap">
        <include refid="defaultUser"/>
    </select>

注意: 尽量不要使用分号,sql语句可能会拼接

4.多表查询

1.一对一的映射关系

示例

查询所有操作,同时获取到用户下所有账户的信息(多表查询常用方法)

sql 语句

sql 语句查询所有账户的时候同时获得当前账户的所有信息

select u.*,a.id as aid,a.uid,a.money from account a ,user u where u.id=a.uid;

sql 语句查询所有账户的时候同时获得当前账户的所地址和姓名

select a.*,u.username,u.address from account a ,user u where u.id=a.uid;

1.从表实体应该包含一个主表实体的对象引用

在Account类中

//从表实体应该包含一个主表实体的对象引用
    private Integer id;
    private Integer uid;
    private Double money;

	private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
.......

2.IAccountDao的接口中

public interface IAccountDao {

    /**
     * 查询所有账户
     * @return
     */
    List<Account> findAll();
}

3.AccountDao的mapper中

<mapper namespace="com.dao.IAccountDao">
    <!--定义封装account和user的resultMap-->
    <resultMap id="AccountUserMap" type="com.Account">
        <id property="id" column="aid" />
        <result property="uid" column="uid" />
        <result property="money" column="money" />
        <!--一对一的关系映射,配置封装user的内容 column中指名从表的外键 property="user"指的是单个实体类的引用-->
        <association property="user" column="uid" javaType="User">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="address" column="address"/>
            <result property="sex" column="sex"/>
            <result property="birthday" column="birthday"/>
        </association>
    </resultMap>

    <!--IAccountDao的查询所有-->
    <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>

注意事项中的javaType=“User” 一定要指名主体表的实体类名然后column中指名从表的外键!!

<association property="user" column="uid" javaType="User">

4.AccountDao的测试类中

@Test
public void findAll(){
    List<Account> accounts = accountDao.findAll();
    for (Account account:accounts) {
       System.out.println("每一个account的信息");
       System.out.println(account);
       System.out.println(account.getUser());
     }
}

第二种方法是创建一个AccountUser类

不建议使用

public class AccountUser extends Account {
    private String username;
    private String address;
	........//get和set方法和toString方法
}

2.一对多的关系映射

示例:用户和账户

一个用户可以有多个账户
一个账户只能属于一个用户(多个账户也可以属于同一个用户)

步骤:

1、建立两张表:用户表 帐户表
      让用户表和账户表之前具备一对多的关系:需要使用外键在帐户表上添加
  2、建立两个实体类:用户实体类和账户实体类
      让用户和账户的实体类能体现出来一对多的关系
  3、配置两个配置文件
      用户的配置文件
      账户的配置文件
  4、实现配置:
      当我们查询用户的时候,可以同时得到用户下所包含的信息
      当我们查询账户时,我们可以同时得到账户所属的用户信息

1.主表实体中应该包含从表实体的集合引用

User类中

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //一对多关系映射,主表实体应该包含从表实体的集合应用
    private List<Account> accounts;
	........
}

2.UserDao的接口中

//  查询所有用户
 List<User> findAll();

3.UserDao的mapper中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.dao.IUserDao">
    <!--定义封装user和account的resultMap type主表实体类-->
    <resultMap id="userAccountMap" type="User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <result property="birthday" column="birthday"></result>
        <!--一对多的关系映射,配置user封装accounts的内容-->
        <!--其中的property指的是从表的集合引用 ofType从表实体类-->
        <collection property="accounts" ofType="com.Account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>

    <!--配置查询所有  其中id不能乱写必须是dao接口中的方法  resultType写的是实体类的全路径-->
    <select id="findAll" resultMap="userAccountMap">
          <!--select * from user u left outer join account a on u.id = a.uid-->
        select u.*,a.id as aid,a.uid,a.money from user u left outer join account a on u.id=a.uid
        <!---使用这条语句把a表的id别名为aid,否则u表和a表的id一样,没有起别名的话在上面的配置中无法给		         accounts的id属性赋值,会赋值上user表的id-->
    </select>
</mapper>

4.UserDao的测试类中

//查询所有(一个用户下的账号信息)
    @Test
    public  void findAll( ) throws Exception {
        List<User> users = userDao.findAll();
        for (User user:users) {
            System.out.println("每个用户的信息");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }

3.多对多的关系映射

示例:用户与角色

一个用户可以有多个角色
一个角色可以赋予多个用户

步骤:

1、建立两张表:用户表 用户表
      让用户和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
  2、建立两个实体类:用户实体类和角色实体类
      让用户和角色的实体类能体现出来多对多的关系
      各自包含对方一个集合引用
  3、配置两个配置文件
      用户的配置文件
      角色的配置文件
  4、实现配置:
      当我们查询用户的时候,可以同时得到用户下所包含角色的信息
      当我们查询角色时,我们可以同时得到角色所赋予的用户信息

多对多的关系映射需要一个中间表。

sql语句(多表外链接查询语句)

select u.*, r.id as rid,r.role_name,r.role_desc from role r   
left outer join user_role ur on r.id = ur.rid   
left outer join user u on u.id=ur.uid  

注意其中每行末尾的空格

**目的:**为了查询角色下的用户信息

**步骤:**以role表(别名)为主表,左外连接user_role表(别名ur,此表为中间表),连接条件r.id = ur.rid 。
再以这两个表组合的表左外连接user表,连接条件u.id=ur.uid 。

注意:数据库连接(内链接,外连接(左连接,右连接)

1.一个实体表中包含另一个实体表的集合引用

由于是多对多的关系所有不分从表和主表

public class Role implements Serializable {

    private Integer roleId;
    private String roleName;
    private String roleDesc;

    /**
     * 多对多的关系映射:一个角色可以赋予多个用户
     */
    private List<User> users;
    .......
}

2.IRoleDao接口中定义该方法

public interface IRoleDao {
    /**
     * 查询所有角色
     * @return
     */
    List<Role> findAll();
}

3.IRoleDao的mapper中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dao.IRoleDao">
    <!--定义role表的resultmap-->
    <resultMap id="roleMap" type="com.Role">
        <!--此处id的column由于sql语句中将role表中的id改为rid,故column也要变为rid-->
        <id property="roleId" column="rid"/>
        <result property="roleName" column="role_name"/>
        <result property="roleDesc" column="role_desc"/>
        <collection property="users" ofType="com.User">
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="address" property="address"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
        </collection>
    </resultMap>

    <!--查询所有-->
    <select id="findAll" resultMap="roleMap">
        select u.*, r.id as rid,r.role_name,r.role_desc from role r
         left outer join user_role ur on r.id=ur.rid
         left outer join user u on u.id = ur.uid
    </select>
</mapper>

4.RoleDao的测试类中

@Test
    public void findAll(){
        List<Role> roles  = roleDao.findAll();
        for (Role role:roles) {
            System.out.println("=====每个角色的信息=====");
            System.out.println(role);
            System.out.println(role.getUsers());
        }
    }

同理可得用户的全部信息与用户的角色

sql语句

select u.*, r.id as rid,r.role_name,r.role_desc from user u
left outer join user_role ur on u.id = ur.uid
left outer join role r on r.id=ur.rid

1.实体类User包含对Accounts的引用

@Getter
@Setter
@ToString
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    //实体类User包含对Accounts的引用
    private List<Role> roles;
}

2.UserDao的接口中定义该方法

  //  查询所有
    List<User> findAll();

3.UserDao的mapper中

 <resultMap id="userMap" type="com.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>
     
        <collection property="roles" ofType="com.Role">
            <id property="roleId" column="rid"></id>
            <result property="roleName" column="role_name"></result>
            <result property="roleDesc" column="role_desc"></result>
        </collection>
    </resultMap>


    <select id="findAll" resultMap="userMap">
       select u.*,r.id as rid,r.role_name,r.role_desc from user u
		left outer join user_role ur on u.id = ur.uid
		left outer join role r on r.id=ur.rid
    </select>

4.UserDao的测试类中

 @Test
    public  void findAll( ) throws Exception {
		List<User> users = userDao.findAll();
        for (User user:users) {
            System.out.println("====每个用户的信息====");
            System.out.println(user);
            System.out.println(user.getRoles());
        }
    }

4.association与collection的区别

一对一用的是association,一对多以及多对多用的是collection。

5.延迟加载和缓存

1.Mybatis中的延迟加载

​ 问题:在一对多中,当我们有一个用户,它有100个账户。
​ 在查询用户的时候,要不要把关联的账户查出来?
​ 在查询账户的时候,要不要把关联的用户查出来?

在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

什么是延迟加载
在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
什么是立即加载
不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多
一对多,多对多:通常情况下我们都是采用延迟加载。
多对一,一对一:通常情况下我们都是采用立即加载。

1.一对一延迟加载

1.在SqlMapConfig.xml中配置setting标签

<settings>
        <!-- 配置全局缓存-->
	<setting name="lazyLoadingEnabled" value="true"/>
	<setting name="aggressiveLazyLoading" value="false"/>
</settings>

2.在IAccoutDao.xml中配置association标签

   <!--定义封装account和user的resultMap-->
    <resultMap id="accountUserMap" type="Account">
        <id property="id" column="id"></id>
   		<result property="uid" column="uid"></result>
   		 <result property="money" column="money"></result>
	 <!--一对一的关系映射,配置封装user的内容
  	  select属性的内容,查询用户的唯一标识符
   	 column属性的内容:用户根据id查询时,所需要参数的值-->
   		 <association property="user" column="uid" javaType="User" 	                             		 	     		   select="com.dao.IUserDao.findId">
   	 	</association>
	</resultMap>

<select id="findAll" resultMap="accountUserMap">
    select * from  account
</select>

3.测试类

 @Test
    public void testFindAll(){
        List<User> users=userDao.findAll();
        for(User user: users) {
            System.out.println("--------------------");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }

2.一对多延迟查询

和一对一没有太多区别

2.在IUserDao.xml中配置collection标签

<!--定义封装account和user的resultMap-->
    <resultMap id="accountUserMap" type="Account">
         <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <result property="birthday" column="birthday"></result>

        <!--一对一的关系映射,配置封装user的内容
        select属性的内容,查询用户的唯一标识符
        column属性的内容:用户根据id查询时,所需要参数的值--> 
    	<collection property="accounts" ofType="Account"                                                             select="com.dao.IAccountDao.findByUid" column="id">
        </collection>
    </resultMap> 

2.Mybatis中的缓存

什么是缓存
存在于内存中的临时数据。
为什么使用缓存
减少和数据库的交互次数,提高执行效率。
什么样的数据能使用缓存,什么样的数据不能使用
适用于缓存:
经常查询并且不经常改变的。
数据的正确与否对最终结果影响不大的。
不适用于缓存:
经常改变的数据
数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。****

1.一级缓存

​ 它指的是Mybatis中SqlSession对象的缓存。
​ 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
​ 该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
​ 查询是否有,有的话直接拿出来用。
​ 当SqlSession对象消失时,mybatis的一级缓存也就消失了。

mybatis中默认就是一级缓存了(平时的测试类就是一级缓存存在SqlSession中)

测试:

select * from user

测试类:

@Test
    public void testFirstLevelCache(){
        User user1 = userDao.findId(42);
        System.out.println(user1);
        User user2 = userDao.findId(42);
        System.out.println(user2);

        System.out.println(user1==user2);
    }   

结果表明使用了一级缓存

不使用一级缓存的方法:

关闭session并重新启动或直接session.clearCache()

@Test
    public void testFirstLevelCache(){
        User user1 = userDao.findId(42);
        System.out.println(user1);

      //  session.close();
    //    session = factory.openSession();
       
        session.clearCache();
        userDao = session.getMapper(IUserDao.class);   //两种方法都需要这条语句
        User user2 = userDao.findId(42);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

注意:
当调用SqlSession的修改、添加、删除、commit()、close()等方法时,都会清空一级缓存。

2.二级缓存

它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)

1.SqlMaoConfig.xml中

<settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.在需要使用二级缓存的实体类的mapper中 (IUserDao.xml)

<!--开启user支持二级缓存-->
    <cache/>
 <!-- 根据id查询用户   注意属性useCache -->
    <select id="findAll" resultType="com.User" useCache="true">
        select * from user
    </select>

3.测试类

public class SecondLevelCacheTest {

    private InputStream in;
    private  SqlSessionFactory factory;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);

    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        in.close();
    }

    /**
     * 测试二级缓存
     */
    @Test
    public void testSecondLevelCache(){
        SqlSession sqlSession1 = factory.openSession();
        IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = dao1.findById(41);
        System.out.println(user1);
        sqlSession1.close();   //一级缓存消失

        SqlSession sqlSession2 = factory.openSession();
        IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
        User user2 = dao2.findById(41);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1 == user2);  //输出false,因为二级缓存存的是数据,不是对象
    }
}

注意:
在SqlSessionFactory的二级缓存中是数据,不是对象!谁来拿数据,它就会把数据拿来创建一个新对象。

6.注解开发的环境搭建

基本与使用xml开发一样,需要注意的是不能有映射文件的存在,只能有SqlMapConfig.xml

SqlMapConfig.xml

<?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">
<configuration>
    <properties resource="jdbcConfig.properties"></properties>

    <typeAliases>
        <package name="com.domain"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>          
        </environment>
    </environments>

    <mappers>
        <!-- <mapper source=" ">  :这种引用方式是引用类路径下的映射xml文件 -->
        
        <!-- 指定带有注解的dao接口所在位置 -->
        <!-- <mapper class="com.itheima.dao.IUserDao"></mapper> 一个接口 -->
        <package name="com.dao"/>    <!-- 包下所有接口 -->
    </mappers>
</configuration>

7.注解开发的crud

1.IUserDao接口中使用注解

public interface IUserDao {

    /**
     * 查询所有用户
     */
    @Select("select * from user")
    List<User> findAll();

    /**
     * 保存用户
     */
    @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
    void saveUser(User user);

    /**
     * 更新用户
     */
    @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
    void updateUser(User user);

    /**
     * 删除用户
     */
    @Delete("delete from user where id=#{id} ")
    void deleteUser(Integer userId);

    /**
     * 根据id查询用户
     */
    @Select("select * from user  where id=#{id} ")
    User findById(Integer userId);

    /**
     * 根据用户名称模糊查询
     */
 //    @Select("select * from user where username like #{username} ")    这个调用方法的参数要加%
    @Select("select * from user where username like '%${value}%' ")
    List<User> findUserByName(String username);

    /**
     * 查询总用户数量
     */
    @Select("select count(*) from user ")
    int findTotalUser();
}

2.测试类

	@Test
    public void testSave(){
        User user = new User();
        user.setUsername("mybatis annotation");
        user.setAddress("北京市昌平区");

        userDao.saveUser(user);
    }

    @Test
    public void testUpdate(){
        User user = new User();
        user.setId(57);
        user.setUsername("mybatis annotation update");
        user.setAddress("北京市海淀区");
        user.setSex("男");
        user.setBirthday(new Date());

        userDao.updateUser(user);
    }


    @Test
    public void testDelete(){
        userDao.deleteUser(51);
    }

    @Test
    public void testFindOne(){
        User user = userDao.findById(57);
        System.out.println(user);
    }


    @Test
    public  void testFindByName(){
  //        List<User> users = userDao.findUserByName("%mybatis%");   这个对应#{value}
        List<User> users = userDao.findUserByName("mybatis");  //这个对应'%${value}%'
        for(User user : users){
            System.out.println(user);
        }
    }

    @Test
    public  void testFindTotal(){
        int total = userDao.findTotalUser();
        System.out.println(total);
    }

8.注解开发解决数据库列名与实体类的属性名不对应

在Dao接口里一个方法前使用@Results,因为起了对应关系别名,所以在别的方法前使用@ResultMap(“别名”)。

IUserDao

public interface IUserDao {
    @Select("select * from user")
    @Results(id="userMap",value = {           //这里的id是起个名。    
            @Result(id=true, column = "id" ,property ="userId" ),  //这里的id是主键
            @Result(column = "sex" ,property ="userSex" ),
            @Result(column = "username" ,property ="userName" ),
            @Result(column = "birthday",property ="userBirthday" ),
            @Result(column = "address" ,property ="userAddress" ),
    })
    List<User>  findAll();

    @Select("select * from user where id=#{id}")
    @ResultMap("userMap")
    User findById(Integer id);

    @Select("select * from user where username like #{username}")
    @ResultMap("userMap")
    List<User> findByName(String username);

}

9.注解开发的多表查询

可以这么记忆:
你所需要对应的表是很多个注解用many
对应一个的话就用one

1.多对一(一对一)

1.IAccountDao接口中使用注解

public interface IAccountDao {
    /**
     * 查询所有账户,并且获取每个账户所属的用户信息
     */
    @Select("select * from account")
    @Results(id="accountMap",value = {
            @Result(id=true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            //这个注解是引入主表     FetchType(加载时机)  EAGER(立即加载) LAZY(延时加载)
            @Result(property = "user",column = "uid",one=@One(select="com.dao.IUserDao.findById",fetchType= FetchType.EAGER))
    })
    List<Account> findAll();

    /**
     * 根据用户id查询账户信息
     */
    @Select("select * from account where uid = #{uid}")
    List<Account> findAccountByUid(Integer userId);
}

2.Account类中(从表类)

 //多对一(mybatis中称之为一对一)的映射:一个账户只能属于一个用户
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

3.测试类

    @Test
    public void findAllTest(){
        List<Account> accounts = accountDao.findAll();
        for(Account account:accounts) {
            System.out.println("-----------");
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }

2.一对多(多对多)

1.IUserDao接口中使用注解

public interface IUserDao {

    /**
     * 查询所有用户
     */
    @Select("select * from user")
    @Results(id="userMap",value={
            @Result(id=true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(property = "accounts",column = "id",          //一对多使用many
                    many = @Many(select = "com.dao.IAccountDao.findAccountByUid",
                                fetchType = FetchType.LAZY))    //延时加载
    })
    List<User> findAll();

    /**
     * 根据id查询用户
     */
    @Select("select * from user  where id=#{id} ")
    @ResultMap("userMap")
    User findById(Integer userId);

}

2.User类中(从表类)

 //一对多关系映射:一个用户对应多个账户
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

10.注解开发开启二级缓存

哪个Dao接口需要就写在哪儿(比如:IUserDao)

IUserDao

@CacheNamespace(blocking = true)   //开启二级缓存
public interface IUserDao {
    ......
}

11.半注解开发

既要使用注解开发同时使用映射文件xml开发,可在映射文件xml使用

实例:

<!-- namespace属性必须是dao接口的全限定类名,这样dao接口的sql注解就能被扫描 -->
<mapper namespace="com.hrms.mapper.EmployeeMapper">
    <select id="selectOneById" parameterType="java.lang.Integer" resultType="com.hrms.bean.Employee">
        SELECT
        emp_id empId, emp_name empName, emp_email empEmail, gender, department_id departmentId
        FROM
        <include refid="table_name"/>
        WHERE
        emp_id = #{empId}
    </select>
    
   ......
</mapper>

12.@Param注解:接口方法多参数传递

使用Mybatis的注解功能,在定义接口方法的时候声明SQL中使用参数名

接口中的方法:

public Person getPersonByIdAndByName(@Param("Id") int id,@Param("Name") String name);

mapper.xml中的SQL:

<select id="getPersonByIdAndByName" resultMap="BaseResultMap">
	select * from person where id=#{Id} and name=#{Name}
</select>

chType = FetchType.LAZY)) //延时加载
})
List findAll();

/**
 * 根据id查询用户
 */
@Select("select * from user  where id=#{id} ")
@ResultMap("userMap")
User findById(Integer userId);

}


2.User类中(从表类)

```java
 //一对多关系映射:一个用户对应多个账户
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

10.注解开发开启二级缓存

哪个Dao接口需要就写在哪儿(比如:IUserDao)

IUserDao

@CacheNamespace(blocking = true)   //开启二级缓存
public interface IUserDao {
    ......
}

11.半注解开发

既要使用注解开发同时使用映射文件xml开发,可在映射文件xml使用

实例:

<!-- namespace属性必须是dao接口的全限定类名,这样dao接口的sql注解就能被扫描 -->
<mapper namespace="com.hrms.mapper.EmployeeMapper">
    <select id="selectOneById" parameterType="java.lang.Integer" resultType="com.hrms.bean.Employee">
        SELECT
        emp_id empId, emp_name empName, emp_email empEmail, gender, department_id departmentId
        FROM
        <include refid="table_name"/>
        WHERE
        emp_id = #{empId}
    </select>
    
   ......
</mapper>

12.@Param注解:接口方法多参数传递

使用Mybatis的注解功能,在定义接口方法的时候声明SQL中使用参数名

接口中的方法:

public Person getPersonByIdAndByName(@Param("Id") int id,@Param("Name") String name);

mapper.xml中的SQL:

<select id="getPersonByIdAndByName" resultMap="BaseResultMap">
	select * from person where id=#{Id} and name=#{Name}
</select>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值