学习:JAVAEE_7 框架概述/Log4j日志/MyBatis

框架概述

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。

简而言之,框架是软件(系统)的半成品,框架封装了很多的细节,使开发者可以使用简单的方式实现功能,大大提高开发效率。

一句话解释: 框架其实就是一套模板,或者可重用的设计,套路。按照这种套路来写代码。尤其是在开发大型项目上,框架的作用体现得就更加淋漓尽致。小型项目,没有框架的用武之地。!

框架要解决的问题

框架要解决的最重要的一个问题是技术整合的问题,在 JAVA EE 的 框架中,有着各种各样的技术,不同的软件企业需要从J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

 框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。

代码是要分层,项目要分层,每一层的代码需要进行交互,对话。

Log4j日志

我们在使用MyBatis的时候, 其实MyBatis框架会打印一些必要的日志信息, 在开发阶段这些日志信息对我们分析问题,理解代码的执行是特别有帮助的; 包括项目上线之后,我们也可以收集项目的错误日志到文件里面去; 所以我们采用专门的日志系统来处理.

步骤

引入依赖

<!-- log start -->
<!-- 日志的具体实现  -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>

<!--  接口  -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.6</version>
</dependency>

<!-- 中间的转化jar包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>

在resources文件夹下,创建一个文件 log4j.properties 这个名字是固定的!

##设置日志记录到控制台的方式
log4j.appender.std=org.apache.log4j.ConsoleAppender
log4j.appender.std.Target=System.err
log4j.appender.std.layout=org.apache.log4j.PatternLayout
log4j.appender.std.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

##设置日志记录到文件的方式
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=mylog.txt
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

##日志输出的级别,以及配置记录方案
log4j.rootLogger= debug,std,file	
## 级别:error > warn > info>debug>trace  

MyBatis

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

mybatis 通过xml 或注解的方式将执行的各种statement 配置起来,并通过java 对象和statement 中sql的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql并将结果映射为 java 对象并返回。采用 ORM ( Object relational mapping)思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与 jdbc api打交道,就可以完成对数据库的访问操作。

官网: MyBatis 3 | Introduction – mybatis

核心配置文件

在resources 下面创建 SqlMapConfig.xml 配置文件 <!--核心配置文件-->

核心配置文件中属性的顺序

属性说明
properties引入外部properties文件
settings全局配置参数
typeAliases类型别名
typeHandlers类型处理器
objectFactory对象工厂
plugins插件
environments环境集合属性对象
mappers映射器

新建外部jdbc.properties配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_day01?characterEncoding=utf-8
jdbc.user=root
jdbc.password=root

核心配置文件

<?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文件-->
<properties resource="db.properties"/>

<!-- 开启redis二级缓存 默认就是开启的可以不进行设置-->
<settings>
 <setting name="cacheEnabled" value = "true"/> 
</settings>

<!-- 给我们自己的类起别名-->
<typeAliases>
 <!-- 给指定的类起别名 type: 具体的类  alias : 起的别名 -->
 <!--<typeAlias type="com.zml.pojo.User" alias="user"/>-->

 <!-- 给指定的类起别名 type: 具体的类 ,它的别名就是它的类名,大小写无所谓。起了别名的类则可以直接使用别名而无需再写全路径名 -->
 <typeAlias type="com.zml.pojo.User"/>
 <!--给这个包下的所有类起别名,它的别名就是它的类名,大小写无所谓。-->
 <package name="com.zml.pojo"/>
</typeAliases>

<!-- mybatis 分页插件 -->
<plugins>
 <plugin interceptor="com.github.pagehelper.PageInterceptor">
 </plugin>
</plugins>

<!--  告诉mybatis去连哪个数据库,使用什么账号和密码去连。-->
<environments default="outer">

 <!--定义一个环境 用来定义数据库在哪里? 账号和密码
      可以定义很多的环境
  -->
 <environment id="development">
   <!--配置事务,MyBatis事务用的是jdbc ,采用jdbc那一套事务,-->
   <transactionManager type="JDBC"/>
   <!--配置连接池, POOLED:使用连接池(mybatis内置的); UNPOOLED:不使用连接池-->
   <dataSource type="POOLED">
     <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://localhost:3306/mybatis_day01"/>
     <property name="username" value="root"/>
     <property name="password" value="root"/>
   </dataSource>
 </environment>
 <environment id="outer">
   <transactionManager type="JDBC"/>
   <dataSource type="POOLED">
     <property name="driver" value="${jdbc.driver}"/>
     <property name="url" value="${jdbc.url}"/>
     <property name="username" value="${jdbc.user}"/>
     <property name="password" value="${jdbc.password}"/>
   </dataSource>
 </environment>
</environments>

<!-- 还要告诉mybatis,我们有哪些映射文件-->
<mappers>
 <!-- 引入映射文件路径 -->
 <mapper resource="com/zml/dao/UserDao.xml"/>
 <!-- 配置单个接口 -->
 <mapper class="com.zml.dao.UserDao"></mapper>
 <!-- 批量配置 -->
 <package name="com.zml.dao"></package>
</mappers>

</configuration>

映射文件

<!--采用别名的方式-->
<select id="findByUid02" parameterType="int" resultType="user">
  select * from t_user where uid = #{uid}
</select>

Mybatis 连接池与事务

在 Mybatis 的 SqlMapConfig.xml 配置文件中, 通过 <dataSource type=”pooled”>来实现 Mybatis 中连接池的配置.

  • UNPOOLED 不使用连接池的数据源

  • POOLED 使用连接池的数据源

  • JNDI 使用 JNDI 实现的数据源,不一样的服务器获得的DataSource是不一样的. 注意: 只有是web项目或者Maven的war工程, 才能使用. 我们用的是tomcat, 用的连接池是dbcp.

MyBatis 在初始化时,解析此文件,根据<dataSource>的 type 属性来创建相应类型的的数据源DataSource,即:

type=”POOLED”: MyBatis 会创建 PooledDataSource 实例, 使用连接池 ​ type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例, 没有使用的,只有一个连接对象的 ​ type=”JNDI”: MyBatis 会从 JNDI 服务上(tomcat ... jboss...)查找 DataSource 实例,然后返回使用. 只有在web项目里面才有的,用的是服务器里面的. 默认会使用tomcat里面的dbcp

事务的开启<!--sqlSessionFactory.openSession(true)-->

//1. 读取核心配置文件,读取成一个输入流
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

//2. 创建出来sqlsessionfactory的构建器
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

//3. 根据构建器,创建sqlsessionfactory、
// 把sqlsession 认识成 连接对象即可 ,  SqlSessionFactory 就是连接工厂,有点类似连接池!
SqlSessionFactory sqlSessionFactory = builder.build(is);

//4. 根据sqlsessionfactory , 创建sqlsession
SqlSession session = sqlSessionFactory.openSession(true);  // 默认是false 关闭自动提交事务,设置为true则开启自动提交事务

//5. 根据sqlsession, 创建UserDao的代理对象
UserDao userDao = session.getMapper(UserDao.class);

//6. 调用findAll方法
List<User> list = userDao.findAll();
System.out.println("list=" + list);

//7. 关闭sqlsession
session.close();

Mybatis入门

创建Maven工程(jar)pom文件中导入坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.zml</groupId>
 <artifactId>myBatisDemo</artifactId>
 <version>1.0-SNAPSHOT</version>

 <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
 </properties>
 <!--1. 添加依赖-->
 <dependencies>
     <!--MyBatis坐标-->
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.6</version>
     </dependency>
     <!--mysql驱动-->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.22</version>
     </dependency>
     <!--单元测试-->
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.10</version>
         <scope>test</scope>
     </dependency>

     <!--lombok 依赖-->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.8</version>
     </dependency>
 </dependencies>

</project>

数据库对应实体对象

package com.zml.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

创建Dao接口

package com.zml.dao;
import com.zml.pojo.User;
import java.util.List;
public interface UserDao {
  List<User> findAll();
}

在resources 下面创建文件夹 com/zml/dao/UserDao.xml 文件的名字尽量保持与dao包的名字一样。

<?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">

<!--
1. mapper里面的namespace 一定要写,否则报错:
    Mapper's namespace cannot be empty   namespace一定要写

2. namespace里面一定要Dao接口的全路径名,否则报错:
   Type interface com.zml.dao.UserDao is not known to the MapperRegistry.
   UserDao这个接口,在映射器注册中心里面没找到。
-->
<mapper namespace="com.zml.dao.UserDao">

  <!--2. 定义查询的语句
        id : UserDao里面的方法名字 不能写错!
        resultType: 方法的返回值类型, 如果是一个集合类型,那么里面只需要写 元素的类型即可。
            因为如果这个方法查询得到很多条记录回来,mybatis会判定数量的,如果超过一条,它会使用List集合来装
            所以我们只要写上集合里面装的类型即可,这也要写全路径名!
    -->
  <select id="findAll" resultType="com.zml.pojo.User">
    <!-- 3. 写上查询的sql语句  findAll这个方法背后执行的sql语句是什么,要自己写。 -->
    select * from t_user;
  </select>

</mapper>

在resources 下面创建 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>

  <!-- 1. 告诉mybatis去连哪个数据库,使用什么账号和密码去连。-->
  <environments default="development">

    <!--定义一个环境 用来定义数据库在哪里? 账号和密码
            可以定义很多的环境
        -->
    <environment id="development">
      <!--配置事务,MyBatis事务用的是jdbc ,采用jdbc那一套事务,-->
      <transactionManager type="JDBC"/>
      <!--配置连接池, POOLED:使用连接池(mybatis内置的); UNPOOLED:不使用连接池-->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_day01"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>

  <!--2. 还要告诉mybatis,我们有哪些映射文件-->
  <mappers>
    <mapper resource="com/zml/dao/UserDao.xml"/>
  </mappers>

</configuration>

定义测试类用来测试

package com.zml.test;
import com.zml.dao.UserDao;
import com.zml.pojo.User;
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 org.junit.Test;
import java.io.InputStream;
import java.util.List;

public class TestUserDao {

  //测试UserDao的findAll方法
  @Test
  public void testFindAll() throws Exception {

    //1. 读取核心配置文件,读取成一个输入流
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

    //2. 创建出来sqlsessionfactory的构建器
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

    //3. 根据构建器,创建sqlsessionfactory、
    // 把sqlsession 认识成 连接对象即可 ,  SqlSessionFactory 就是连接工厂,有点类似连接池!
    SqlSessionFactory sqlSessionFactory = builder.build(is);

    //4. 根据sqlsessionfactory , 创建sqlsession
    SqlSession session = sqlSessionFactory.openSession();

    //5. 根据sqlsession, 创建UserDao的代理对象
    UserDao userDao = session.getMapper(UserDao.class);

    //6. 调用findAll方法
    List<User> list = userDao.findAll();
    System.out.println("list=" + list);

    //7. 关闭sqlsession
    session.close();

  }
}

Mybatis完成CRUD

SqlSessionFactory工具类的抽取

/**
 * 专门用于返回sqlsession
 */
public class SqlSessionFactoryUtil {

  private static SqlSessionFactory factory;

  //1. 构建一个sqlsessionfactory
  static{
    try {
      //1.1 读取配置文件
      InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

      //1.2 构建构建器
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      //1.3 构建工厂
      factory = builder.build(is);

      //1.4 关流
      is.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  //2. 对外暴露一个方法,共他人获取连接
  public static  SqlSession getSession(){
    return factory.openSession();
  }

  //3. 关闭session
  public static void closeSession(SqlSession session){
    session.close();
  }

  //4. 关闭session,关闭之前先提交
  public static void closeSessionAndCommit(SqlSession session){
    session.commit();
    session.close();
  }
}

新增用户

接口中添加方法

public interface UserDao {   
/**
     * 新增用户
     * @param user
     * @return 影响的行数
     */
  int add(User user);
}

在 UserDao.xml 文件中加入新增配置

<!--2. 添加用户
            id要写方法名字
            parameterType :参数的类型
            增删改不需要写返回类型,默认mybatis会自己把影响的行数给返回。

            #{} 就是取对象中的属性值,当然也可以取参数的值。
    -->
<insert id="add" parameterType="com.zml.pojo.User">
  insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address})
</insert>

添加测试类中的测试方法

@Test
public void testAdd() throws IOException {

  //1. 读取核心配置文件
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

  //2. 创建构建器
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

  //3. 创建sqlsessionfactory
  SqlSessionFactory sessionFactory = builder.build(is);

  //4. 创建sqlsession
  SqlSession session = sessionFactory.openSession();

  //5. 创建代理
  //Type interface com.zml.dao.UserDao is not known to the MapperRegistry.
  UserDao userDao = session.getMapper(UserDao.class);

  //6. 调用方法
  User user = new User();
  user.setUsername("admin");
  user.setSex("男");
  user.setBirthday(new Date());
  user.setAddress("深圳");

  int row = userDao.add(user);
  System.out.println("row=" + row);

  // 新增操作执行完毕之后,即可获取到user对象的id值,
  //但是目前获取不了,因为这个方法add里面的U映射文件没有配置
  //System.out.println("id=" + user.getUid());

  //7. 默认情况下mybatis把连接的自动提交的事务给关闭了,所以只要是增删改,都需要手动提交事务
  session.commit();

  //8. 关闭session
  session.close();
}

新增用户 id 的返回值

  • 有时候我们存在一些场景,把一条数据存储到数据库了之后,需要马上得到这条记录的id值。因为这条数据是我们自己创建赶出来的,它的id是多少,我们是不知道的,只有这条记录已经存在于数据库里面了,才知道它的id值。

  • 什么情况下|场景下,我们需要在插入完这条记录之后,立即就需要得到这条记录的id呢?

    当我们往A表里面添加一条记录之后,需要继续往B表里面添加一条记录,但是A表和B表形成了一种主外键关系。B表里面的外键就是A表里面的主键,所以往B表里面添加记录,必须要先知道A表的这条记录的主键

新增用户后, 同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。

  • 方式一 标签SelectKey获取主键

属性描述
keyPropertyselectKey 语句结果应该被设置的目标属性。
resultType结果的类型。MyBatis 通常可以算出来,但是写上也没有问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。
order这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素-这和如 Oracle 数据库相似,可以在插入语句中嵌入序列调用。
<!--新增用户之后,获取id值-->
<insert id="add" parameterType="com.zml.pojo.User">
  <!--
                 设置添加完毕之后,返回id值
                 selectKey : 用于返回id主键
                     keyProperty :使用User里面的什么属性来装id的值。
                     resultType :主键的类型,java类型
                    order :  这个查询主键的动作,是发生在添加完记录之后再执行还是添加记录之前就执行?
                         这里面能写的只有两个:  BEFORE |  AFTER 一般写AFTER
             -->
  <selectKey keyProperty="uid" resultType="int" order="AFTER">
    <!--这里面还要写一串固定的语法 : 表示查询上一次插入完毕之后的id值是多少。-->
    select LAST_INSERT_ID()
  </selectKey>

  insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address})

</insert>

方式二属性配置

在isnert标签里面直接使用属性keyProperty 和 useGeneratedKeys来设置获取主键id值。

<!--
        新增用户之后,获取id值 : 方式二
            keyProperty : 使用user里面的什么属性来装主键
            useGeneratedKeys : 使用数据库的自增的这一套方法,使用数据库的主键id.
                一般写就是true , 对应于mysql的数据库,这里写的就是true
                如果写成false,就表明不使用数据库自增的那一套方法得到的id , 使用mybatis自己维护的id值。
                    也就是说,id值,由mybatis提供。
                    oracle 不支持自增。
    -->
<insert id="add" parameterType="com.zml.pojo.User" keyProperty="uid" useGeneratedKeys="true">
  insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address})
</insert>

  **新增用户 id 的返回值(字符串类型)**

  - 主键不一定是int类型... 主键也可以是字符串类型。只有到以后做项目,有数据库合并,数据库集群的时候。

    字符串类型的主键通常就是UUID生成的一串32个字符的字符串。  数据库合并!

        <!--新增用户之后,获取id值 字符串-->
        <insert id="add" parameterType="com.zml.pojo.User">

            <selectKey keyProperty="uid" resultType="String" order="AFTER">
                <!--由mybatis自己维护主键,返回uuid生成的主键值。-->
                select uuid()
            </selectKey>
            insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address})
        </insert>

修改用户

接口中添加修改方法

User findByUid(int id);
/** 要想更新用户,必须先查询用户。
     * 更新用户
     * @param user
     * @return
     */
int  update(User user);

在 UserDao.xml 文件中加入新增配置

<!--更新用户-->

<!--1. 先根据UID来查询用户-->
<select id="findByUid" parameterType="int" resultType="com.zml.pojo.User">
  select * from t_user where uid = #{id}
</select>


<!--2. 更新用户-->
<update id="update" parameterType="com.zml.pojo.User">
  update t_user set username = #{username} , sex = #{sex} , birthday = #{birthday} , address = #{address} where uid = #{uid}
</update>

添加测试类中的测试方法

//更新的测试
@Test
public void testFindById(){
  //找人
  User user = userDao.findByUid(3);
  System.out.println("user=" + user);

  //修改地址
  user.setAddress("深圳");

  //更新。
  int row = userDao.update(user);
  System.out.println("row=" + row);
  session.commit();
}

删除用户

UserDao中添加新增方法

public interface UserDao {
/**
     * 删除用户
      * @param id
     * @return 影响的行数
     */
  int delete(int id);
}
  • 在 UserDao.xml 文件中加入新增配置

<!--删除用户  根据id来删除用户。 -->
<delete id="delete" parameterType="int">
  delete from t_user where uid = #{id}
</delete>
  • 添加测试类中的测试方法

//删除的测试
@Test
public void testDelete(){
  int row = userDao.delete(5);
  System.out.println("row=" + row);
  //要记得提交事务
  session.commit();
}

模糊查询

方式一

UserDao 中添加根据姓氏来查询用户的方法

//模糊查询: 根据用户的姓氏|首字母来查询用户
List<User> findUserByFirstName(String name);
  • 在 UserDao.xml 文件中加入新增配置

<!--模糊查询: 根据姓氏|首字母查询用户-->
<select id="findUserByFirstName" resultType="com.zml.pojo.User" parameterType="java.lang.String">
  select * from t_user where username like #{name}
</select>
  • 添加测试类中的测试方法

//模糊查询: 根据姓氏|首字母查询用户
@Test
public void testFindUerByFirstName(){
  List<User> list = userDao.findUserByFirstName("a%");
  System.out.println("list=" + list);
}

方式二

  • UserDao 中添加新增方法

public interface UserDao {
  //模糊查询: 根据用户的姓氏|首字母来查询用户
  List<User> findUserByFirstName02(String name);
}
  • 在 UserDao.xml 文件中加入新增配置

<select id="findUserByFirstName02" resultType="com.zml.pojo.User" parameterType="java.lang.String">
  select * from t_user where username like '${value}'
</select>
  • 添加测试类中的测试方法

//模糊查询: 根据姓氏|首字母查询用户
@Test
public void testFindUerByFirstName02(){
  List<User> list = userDao02.findUserByFirstName02("a%");
  System.out.println("list02222=" + list);
}

#{}与${}的区别

  • #{}

    • 可以防止sql注入

    • 会对sql语句进行预编译|解析,传递什么参数进来,仅仅是顶替占位#{}而已。

    • 一般使用的都是这个#{}

    • #{} 背后会自动的拼接上 ''

  • ${}

    • 不能防止sql注入

    • 需要手动添加单引号

    • 如果只有一个属性的话必须使用value '${value}'

    • 不会对sql语句进行预先编译,传递什么参数进来,不会仅仅认为这数据,会和sql语句做拼接之后再解析SQL语句 ' or '1=1'

    • 一般比较少用这个,能使用#{}就先使用这个#{}

    • ${} 不会拼接上 '' 所以有时候,我们需要做一些order by 这样的列名指定。

一般来说,只要 #{} 能用的,基本都用它,只要它不能用的时候,再想想这个 ${}

Mybatis的参数深入

parameterType

传递简单类型

  • 基本的类型,字符串

  • 直接写#{任意字段}或者${value}

接口

//传递参数: 传递简单的参数
User findByUserName(String username);

映射文件

<!--
        1. 传递简单的参数
            parameterType :表示参数的类型。 写全路径
            要想获取单一的参数,直接使用#{} 或者 ${} 也行,然后括号里面就写参数的名字
            当然里面也可以写别的,但是一般都写参数的名字,方便阅读。
            User findByUserName(String username);
    -->
<select id="findByUserName" parameterType="java.lang.String" resultType="com.zml.pojo.User">
  select * from t_user where username = #{username}
</select>

传递 pojo 对象

  • Mybatis 使用 ognl 表达式解析对象字段的值, #{}或者${}括号中的值为 pojo 属性名称。

接口

//传递参数: 传递对象类型的参数
User findByUserName(User user );

映射文件

<!--
        2. 传递对象类型的参数
            2.1 parameterType :表示参数的类型,这里传递过来的是一个对象
            2.2 要想从对象里面取出它的某一个属性,直接写属性名即可,无需使用对象.属性这种写法。
             User findByUserName(User user );
            2.3 属性的名字必须要写对,也不能乱写。
    -->
<select id="findByUserName" parameterType="com.zml.pojo.User" resultType="com.zml.pojo.User">
  select * from t_user where username = #{username}
</select>

传递 pojo 包装对象类型

  • 开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo 类中包含 pojo。

pojo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryVo {
  private User user ;
}

接口

//传递参数: 传递包装对象类型
User findByUserName(QueryVo queryVo);

映射文件

<!--
        3. 传递包装对象类型的参数
            3.1 parameterType : 传递的参数类型
            3.2 传递过来的是一个QueryVo的对象,它的里面有一个属性叫做User
            3.3 User这个对象里面,有一个属性username
            3.4 现在想使用username来查询用户数据。

         写属性的时候,直接从参数类型里面的属性开始找起就行了。
    -->

<select id="findByUserName" parameterType="com.zml.pojo.QueryVo" resultType="com.zml.pojo.User">
  select * from t_user where username = #{user.username}
</select>

测试类

//传递包装对象的参数
@Test
public void testFindByUserName03(){
  //1. 获取sqlsession
  SqlSession session = SqlSessionFactoryUtil.getSession();

  //2. 获取代理
  UserDao userDao = session.getMapper(UserDao.class);

  //3. 调用方法
  QueryVo qv = new QueryVo();
  User user = new User();
  user.setUsername("ls");
  qv.setUser(user);


  //传递一个queryvo对象进去,根据用户名来查询用户
  User u = userDao.findByUserName03(qv);
  System.out.println("u=" + u);

  //4. 关闭session
  SqlSessionFactoryUtil.closeSession(session);

}

传递多个参数

  • 在方法的参数上打上注解@param

    如果方法有传递多个参数的必要,那么通常有四种写法来实现:

    • 使用#{0} , #{1} , #{2} ... 来获取指定位置的参数。 0 表示第一个参数,1表示第二个参数。阅读性差。

    • 使用注解@param来给参数起名字。然后在xml里面使用#{参数的别名}取值

    • 可以使用一个Map集合来封装这些数据,然后传递map集合进去。

    • 可以使用一个javaBean来包装这些数据,然后传递javabean的对象进去。

接口

public interface UserDao {

  //根据多个参数来找人。
  //传递参数: 传递多个参数 使用@Param来给参数起别名
  User findUser(@Param("username") String username , @Param("uid") int uid);
}

映射文件

<!--
        ========================传递多个参数============
        1. 采用下标计数法, 0 就表示第一个参数, 1,就表示第二个参数,以此类推...
            一般也不建议使用这种,因为这种方式可读性比较差。数字的可读性比较差
        2. 采用注解的方式。
            在方法的参数上,使用注解@param标记这个参数的名字是什么,然后再xml里面使用#{}来取值

        3. 后面还有很多种法子,但是都是一样的套路,就是把多个参数包装成一个整体
            a. 包装成一个对象,
            b. 包装成一个map
            c. 包装到一个list
   -->

<!--4. 传递多个参数-->
<select id="findUser" resultType="com.zml.pojo.User">
  select * from t_user where username = #{username}  and uid = #{uid}
</select>

resultType

输出简单类型

  • 直接写对应的Java类型. eg: 返回int

    <!--
        1. 返回数据: 返回简单的数据类型
            restultType :表示返回的结果类型,如果返回的是一个简单的数字或者字符串
            那么直接写类型的名字即可 int , string
     -->
    <select id="findCount" resultType="int">
        select count(*) from t_user
    </select>

输出pojo对象

  • 直接写当前pojo类的全限定名 eg: 返回User


<!--2.
        返回数据: 返回对象类型的数据
             restultType :表示返回的结果类型
             如果返回的是一个对象类型,那些的resultType 就是这种类型的全路径
    -->
<select id="findByUid" parameterType="int" resultType="com.zml.pojo.User">
  select * from t_user where uid  = #{uid}
</select>

输出pojo列表

  • 直接写当前pojo类的全限定名 eg: 返回 List<User> list;

<!--
        3. 返回集合类型
            resultType :返回的数据类型
            如果返回的是一个集合,那么不要写集合的类型,只需要写集合里面的元素类型即可。
    -->
<select id="findAll" resultType="com.zml.pojo.User">
  select * from t_user
</select>

resultMap结果类型

  • esultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。

    如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。

    resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询 (多表联合查询)。(下次课讲)

实体:数据库字段是sex 实体对应的是sex66的情况

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int uid;
    private String name;
    private String sex66;
    private Date birthday;
    private String address;
}

接口

public interface UserDao {
  //返回对象类型:  返回对象
  User findByUid(int uid);
}

映射文件

<!--
        解法二: 使用resultMap 来进行数据的映射(属性和列进行映射)
            1. 如果返回的属性名和列名不一样,那么不能使用resultType,必须要使用resultMap
            2. resultMap其实就是一种结果映射。
            3. resultMap标签主要是用来做映射的,
                id :  表示唯一的标识,给这种映射起一个标识。
                type : 这种映射最终封装出来的数据是什么类型。
            4. id这个标签比较特殊一些,官方建议使用这个标签来映射主键
            5. result标签主要是用来映射普通的列
                column : 表示列的名字
                property : 表示类里面属性的名字
如何列名和属性名称相同的话在resultMap中不写对应的属性名
    -->
<resultMap id="userMap" type="com.zml.pojo.User">
  <id column="uid" property="uid" />
  <result column="username" property="username"/>
  <result column="sex" property="sex66"/>
  <!-- 如何列名和属性名称相同的话在resultMap中不写对应的属性名 -->
  <result column="birthday" property="birthday"/>
  <result column="address" property="address"/>
</resultMap>

<!--resultMap 就是告诉mybatis,使用这种映射规则来封装数据-->
<select id="findByUid" parameterType="int" resultMap="userMap">
  select * from t_user where uid = #{uid}
</select>

Mybatis 的动态SQL

动态 SQL 之if标签

QueryVo.java

  • 它的作用专门是用来包装查询对象的。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryVo {
  private User user ;
}

UserDao.java接口

public interface UserDao {

  //根据组合条件(id , username)来查询用户
  User findUser(QueryVo qv);

}

UserDao.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.zml.dao.UserDao">

  <!--根据组合条件来查询用户: id 和  username-->
  <select id="findUser" parameterType="queryvo" resultType="user">
    <!-- 这里的1=1只是一个永远为真的条件,它的作用仅仅是为了保证这个where关键字存在一次而已。-->
    select * from t_user where 1=1

    <!--
            判定id这个值是否不为空,如果不为空,则需要把id添加到查询条件里面去
            如果queryVo里面的user属性不为空,并且user里面的id属性也有值,那么
            即表示想要使用uid来进行条件查询。
         -->
    <if test="user != null and user.uid != 0">
      and uid = #{user.uid}
    </if>

    <!--判定username 是否有值,有值就给它加入到查询条件中-->
    <if test="user != null and user.username != null">
      and username = #{user.username}
    </if>

  </select>
  <!--select * from t_user where uid = ? and username = ?-->
</mapper>

测试类

@Test
public void testFindUser(){

  SqlSession session = SqlSessionFactoryUtil.getSession();
  UserDao userDao = session.getMapper(UserDao.class);

  //设置查询条件
  QueryVo qv = new QueryVo();
  User user = new User();
  // user.setUid(2);
  user.setUsername("ls");
  qv.setUser(user);

  User newUser = userDao.findUser(qv);
  System.out.println("newUser=" + newUser);

  session.close();
}

动态 SQL 之where标签

为了简化上面 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发。

<?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.zml.dao.UserDao02">

<!--需求: 根据性别和用户名的组合条件来查询用户-->
<select id="findUser" parameterType="queryVo" resultType="user">
 select * from t_user

 <where>
   <!--判断用户名是否为空,如果不为空,则添加到查询条件中-->
   <if test="user !=null and user.username !=null ">
     and username = #{user.username}
   </if>

   <!-- 判断性别是否为空,如果不为空,则添加到查询条件中-->
   <if test="user != null and user.sex != null">
     and sex = #{user.sex}
   </if>
 </where>

</select>

<!--select * from t_user-->
<!--select * from t_user where username = ? and sex = ? -->

</mapper>

SQL 片段

Sql 中可将重复的 sql 提取出来,使用时用 include 引入进来,最终达到 sql 重用的目的。我们先到 UserDao.xml 文件中使用<sql>标签,定义出公共部分.

<!--提取共性的sql语句,把这些共性的sql语句称之为 sql片段-->
<sql id="selectAllSql">
select * from t_user
</sql>

<select id="findUserByVo04" parameterType="queryVo" resultType="user">

<!-- select * from t_user -->
<include refid="selectAllSql"></include>


<!-- select * from t_user where username like '张%' and uid in(7,8,9); -->
<where>
 <if test="user != null and user.username != null">
   and username like '${user.username}%'
 </if>
 <!-- and uid in(7,8,9) -->
 <if test="ids != null">
   <!--collection: 遍历的集合  list: 7,8,9
             item: 遍历出来的每一个元素  ,7 |8 |9
             open : 在开始拼接的语句
             separator : 每一次拼接id值之后的间隔字符
             close: 最终拼接的语句 -->
   <foreach collection="ids" item="id" open="and uid in (" separator="," close=")">
     #{id}
   </foreach>
 </if>
</where>
</select>

动态标签之foreach标签

传入多个 id 查询用户信息,用下边sql 实现:

select uid ,username ,birthday ,sex, address from t_user WHERE uid =1 OR uid =2 OR uid=6

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。这样我们将如何进行参数的传递?

dao接口

public interface UserDao03 {

//查询多个id的用户
List<User> findUser(@Param("ids") List<Integer> ids);

}

映射文件

<?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.zml.dao.UserDao03">

<!--
 需求:
     传递进来一个集合,集合里面装很多的id,也有可能不装id,根据这个条件来查询用户-->
<!--
     parameterType: 参数是什么类型就写什么类型。
     resultType:  返回的结果类型,如果返回的是一个集合,那么写它里面的元素类型

     如果传递进来的集合里面装 1, 2, 3 ,那么即表示把id为 1 ,id为2,id为3的用户查询出来
     select uid ,username ,birthday ,sex, address from t_user WHERE  uid =1 OR uid =2 OR uid=6
     select * from t_user  where uid = 1 or uid =  2  or uid = 3 or uid =4
  -->
<select id="findUser" parameterType="list" resultType="user">
 select * from t_user
 <where>
   <!--判断真的有传集合进来  -->
   <if test="ids != null">
     <!--
                 遍历集合,取出来每一个id值,然后拼接到上面的sql语句去
                     collection :遍历的集合是什么
                     item :遍历出来的每一个元素,用什么名字的变量来接受
                     open: 遍历开始之前先补什么字符串到上面的sql语句去
                     close:  遍历结束之后,在上面的sql语句拼接什么内容
                     separator : 遍历每一次之后,间隔什么内容,拼接到sql语句去。
              -->
     <foreach collection="ids" item="id" open="uid =" separator=" or uid =" close="">
       #{id}
     </foreach>
   </if>
 </where>

</select>


</mapper>

测试类

@Test
public void testFindUser(){

  SqlSession session = SqlSessionFactoryUtil.getSession();
  UserDao03 userDao = session.getMapper(UserDao03.class);

  List<Integer> list = new ArrayList<Integer>();
  //list.add(1);
  //list.add(2);
  //list.add(3);
  List<User> userList = userDao.findUser(list);
  System.out.println("userList=" + userList);

  session.close();
}
select uid ,username ,birthday ,sex, address from t_user WHERE uid IN (1,2,3)
<?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.zml.dao.UserDao04">

<!--
     需求:
         传递进来一个集合,集合里面装很多的id,也有可能不装id,根据这个条件来查询用户
      如果传递进来的集合里面装1,2,3
      sql : select * from t_user where uid in (1,2,3)
 -->

<select id="findUser" parameterType="list" resultType="user">

 select * from t_user

 <where>

   <!--判定是否真的有集合 -->
   <if test="ids != null">

     <!--
                   select * from t_user where  uid in ( 1,2,3)
             遍历集合
                 collection :遍历的集合 参数ids
                 item : 遍历出来的每一个元素用什么来接
                 open: 拼接这些遍历出来的数据之前,先拼接什么到sql语句去。
                 separator : 数据的间隔是什么。
                 close : 最终这个语句的末尾是什么。
             -->
     <foreach collection="ids" item="id" open="uid in (" separator="," close=")">
       #{id}
     </foreach>
   </if>
 </where>
</select>
</mapper>

Mybatis 的多表关联查询

一(多)对一

本次案例以简单的用户和账户的模型来分析 Mybatis 多表关系。用户为 User 表,账户为Account 表。一个用户(User)可以有多个账户(Account),但是一个账户(Account)只能属于一个用户(User)。 类比到生活中的: 一个人可以有多张银行卡账户,但是一个银行卡账户只能属于一个人。具体关系如下:

  • 查询所有账户信息, 关联查询账户的用户名和地址

因为一个账户信息只能供一个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。

SELECT a.*,u.username,u.address FROM t_account a,t_user u WHERE a.uid = u.uid内连接

Account.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
    private int aid;
    private double money;
    private int uid;
    //在账户这边,体现这个账户属于哪一个用户。
    //一个账户只能属于一个用户,一般这种情况采用对象来表示。
    private User user01;
}

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User02 {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

AccountDao.java

public interface AccountDao02 {

  //查询所有的账户 , 返回值是一个List集合,里面装Account
  //因为Account里面有一个属性叫做user01,所以这个Account 也足以表示完所有的数据了。
  List<Account> findAll();
}

AccountDao.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.zml.dao.AccountDao">

  <!-- resultMap : 表的列和bean里面的属性映射
        id: 这种映射的名字, 唯一标识符
        type: mybatis封装好数据之后,最终产出的是一个什么类型的对象呀-->
  <resultMap id="accountMap" type="account">
    <id column="aid" property="aid"/>
    <result column="money" property="money"/>
    <result column="uid" property="uid"/>

    <!--
            一个账户只能属于一个用户,那么账户和用户的关系其实就是一(多)对一
            association : 主要是用来表示一对一的关系,即表示这个账户属于哪一个用户
                property: user01 表示Account这个类里面的属性
                javaType : Account这个类里面的属性user01是什么类型。
             result 标签,
                其实就是表示把剩下的address和 username属性封装到
                user02这个类里面的username和address属性上
        -->
    <association property="user01" javaType="User">
      <result column="username" property="username"/>
      <result column="address" property="address"/>
    </association>

  </resultMap>

  <!--由于下面执行的这条语句是查询两张表的数据,返回的结果
    mybatis并不能直接使用Account来装,因为有些列的数据(username, address)
    mybatis不知道怎么装,所以我们需要建立映射规则 , 这里就必须使用resultMap属性 -->
  <select id="findAll" resultMap="accountMap">
    <!-- 查询所有账户信息, 关联查询账户的用户名和地址 -->
    select a.* , u.username , u.address from t_account a , t_user u where a.uid = u.uid;
  </select>

</mapper>

一对多

需求: 查询所有的用户,以及这个用户有哪些账户,都查询出来。

sql : 不能使用内连接了,内连接的核心就是查询出来两张表都有对等关系的记录信息。但是有一种极端的情况: 有的用户可能没有账户。此时就必须使用外连接。其实左外和右外都可以,只是他们的区别就是谁在左边谁在右边而已。

SELECT u.*, a.aid, a.money FROM t_user u LEFT OUTER JOIN t_account a ON u.uid = a.uid

Account.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
  private int aid;
  private double money;
  private int uid;
}

User.java

  • 为了能够让查询的 User 信息中,带有他的个人多个账户信息,我们就需要在 User 类中添加一个集合, 用于存放他的多个账户信息,这样他们之间的关联关系就保存了。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
  //一个用户是可以有多个账户的。
  //如何表示一个用户有多个账户呢?
  //在一的一边,表示多的那一边的关系,通常采用List集合来表示
  private List<Account> accountList;
}

UserDao.java

public interface UserDao {
    //查询所有的用户
    List<User> findAll();
}

UserDao.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.zml.dao.UserDao">

  <!--
        resultMap: 建立映射关系
            id: 这种关系的名字,唯一的标识符
            type : mybatis根据这种关系封装出来的数据对象是什么类型

        -->
  <resultMap id="userMap" type="user">
    <id column="uid" property="uid"/>
    <result column="username" property="username"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    <result column="address" property="address"/>
    <!--
        一个用户可以拥有多个账户,下面要表示这个用户的账户信息都如何映射
        即表示多的那一边的属性如何映射
            collection标签: 用来表示多的那边数据怎么映射
                property: User这个类里面的属性accountList
                ofType : 表示accountList这个集合里面装的元素类型
        -->
    <collection property="accountList" ofType="account">
      <id column="aid" property="aid"/>
      <result column="money" property="money"/>
      <result column="auid" property="uid"/>
    </collection>

  </resultMap>

  <!--查询两张表,得到的数据,不可能直接使用resultType来封装-->
  <select id="findAll" resultMap="userMap">
    <!--  使用左外连接来实现 优先考虑左边的表,左边的表一定全部查询出来,不管有没有账户
            由于mybatis查询两张表的时候,如果存在同名的列,有可能会发生数据错乱的情况,所以需要给其中一个同名的列起别名
            比如现在账户表里面的uid 起别名叫做 auid
       -->
    select u.*,a.aid , a.money , a.uid auid from t_user u left join t_account a on u.uid = a.uid;
  </select>

</mapper>

多对多

需求:实现查询所有角色对象并且加载它所分配的用户信息。

CREATE DATABASE mybatis_day01;
USE mybatis_day01;
CREATE TABLE t_user(
uid int PRIMARY KEY auto_increment,
username varchar(40),
sex varchar(10),
birthday date,
address varchar(40)
);

INSERT INTO `t_user` VALUES (null, 'zs', '男', '2018-08-08', '北京');
INSERT INTO `t_user` VALUES (null, 'ls', '女', '2018-08-30', '武汉');
INSERT INTO `t_user` VALUES (null, 'ww', '男', '2018-08-08', '北京');

CREATE TABLE t_role(
rid INT PRIMARY KEY AUTO_INCREMENT,
rName varchar(40),
rDesc varchar(40)
);
INSERT INTO `t_role` VALUES (null, '校长', '负责学校管理工作');
INSERT INTO `t_role` VALUES (null, '副校长', '协助校长负责学校管理');
INSERT INTO `t_role` VALUES (null, '班主任', '负责班级管理工作');
INSERT INTO `t_role` VALUES (null, '教务处主任', '负责教学管理');
INSERT INTO `t_role` VALUES (null, '班主任组长', '负责班主任小组管理');


-- 中间表(关联表)
CREATE TABLE user_role(
uid INT,
rid INT
);

ALTER TABLE  user_role ADD FOREIGN KEY(uid) REFERENCES t_user(uid);
ALTER TABLE  user_role ADD FOREIGN KEY(rid) REFERENCES t_role(rid);

INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('3', '3');
INSERT INTO `user_role` VALUES ('2', '3');
INSERT INTO `user_role` VALUES ('2', '5');
INSERT INTO `user_role` VALUES ('3', '4');

查询角色我们需要用到 Role 表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。 下面是实现的 SQL 语句:

左外连接:
-- 只要用户的数据和角色表的数据
select  r.* , u.* from t_role r left join user_role ur  on r.rid = ur.rid  
left join t_user u on ur.uid = u.uid

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

Role.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
 private int rid;
 private String rName;
 private String rDesc;
 //一个角色可以有很多的用户担任,那么体现的就是一对多的关系
 private List<User> userList;
}

RoleDao.java

public interface RoleDao {
  List<Role> findAll();
}

RoleDao.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.zml.dao.RoleDao">

<!-- resultMap: 做映射出来
         id :唯一标识符
         type : 封装好了这些数据最终是什么类型的数据 -->
<resultMap id="roleMap" type="Role">
 <id column="rid" property="rid"/>
 <result column="rName" property="rName"/>
 <result column="rDesc" property="rDesc"/>

 <!-- 一个角色是可有多个用户担任的!-->
 <collection property="userList" ofType="user">
   <id column="uid" property="uid"/>
   <result column="username" property="username"/>
   <result column="sex" property="sex"/>
   <result column="birthday" property="birthday"/>
   <result column="address" property="address"/>
 </collection>
</resultMap>


<!--查询所有的角色信息,并且把这个角色有谁担任的用户信息给查询出来-->
<select id="findAll" resultMap="roleMap">
 select  r.* , u.* from t_role r left join user_role ur  on r.rid = ur.rid left join t_user u on ur.uid = u.uid
</select>

</mapper>

MyBatis缓存

作用:

  • 将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取的时候 ,直接从缓存中获取,可以减少和数据库交互的次数,这样可以提升程序的性能!

MyBatis缓存类别

  • 一级缓存:它是sqlSession对象的缓存,自带的(不需要配置)不可卸载的(不想使用还不行). 一级缓存的生命周期与sqlSession一致。

  • 二级缓存:它是SqlSessionFactory的缓存。只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存。二级缓存如果要使用的话,需要我们自己手动开启(需要配置的)。

一级缓存

  1. 一级缓存: 依赖sqlSession对象的, 自带的不可卸载的. 一级缓存的生命周期和sqlSession一致

  2. 一级缓存清空

    • sqlSession销毁 , 调用close()

    • 增删改 提交之后 , 调用了commit

//证明一级缓存的存在
// 前提是开启了log4j日志打印功能,显示出来查询数据
@Test
public void testFindByUid(){

  SqlSession session = SqlSessionFactoryUtil.getSession();
  UserDao userDao = session.getMapper(UserDao.class);

  //查询uid为1的用户数据
  User user = userDao.findByUid(1);
  System.out.println("user=" + user);


  System.out.println("-------------------------------------------");
  //如果这里查询的数据跟前面查询的数据是一样的,都是查询id为1的数据,那么此次查询就不用去查询数据库了
  //而是直接从一级缓存里面拿出来
  User user2 = userDao.findByUid(1);
  System.out.println("user2=" + user2);

  //关闭session
  session.close();

}

//第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。 
如果 sqlSession 去执行 commit操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 commit动作一定会清空缓存,因为有可能存在一种假设: 原来查询的是id为1的用户信息,接着做了更新操作,把id为1的用户信息给修改了,那么此时缓存里面的数据就是过期数据。

二级缓存

二级缓存是SqlSessionFactory的缓存。只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存. 默认mybatis不会开启二级缓存,需要手动配置

在 SqlMapConfig.xml 文件开启二级缓存

<!--配置-->
<configuration>
<!-- 引入properties文件-->
<properties resource="db.properties"/>

<!-- 开启redis二级缓存 默认就是开启的可以不进行设置-->
<settings>
 <setting name="cacheEnabled" value = "true"/> 
</settings>
</configuration>

二级缓存默认是开启的,当然我们可以手动关闭

==因为 cacheEnabled 的取值默认就为 true==,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。

配置相关的 Mapper 映射文件

  • 默认情况下mybatis已经开启了二级缓存,但是默认情况下所有的查询动作都不会把数据放在二级缓存里面。

  • 如果希望某一个查询的动作,把数据存放在二级缓存里里面,那么需要在映射文件中配置

<cache> 标签表示当前这个 mapper 映射将使用二级缓存,即:该mapper文件中的所有查询操作都将使用二级缓存, 无需单独为每一个<select>标签开启。

<mapper namespace="com.zml.dao.UserDao">
  <!--cache 标签会开启该映射文件下的所有查询语句的二级缓存,如果有查询语句不想使用二级缓存的话可以在select标签中天健cache属性进行设置-->
  <cache/>
  <!--	若不想让某个`<select>`标签使用二级缓存,则可以把useCache设置为false 。
 注意: 针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。 -->
  <select id="findByUid" parameterType="int" resultType="user" useCache="false">
    select * from t_user where uid = #{uid}
  </select>
</mapper>

测试类

public class TestUserDao {

    //证明二级缓存的存在
    @Test
    public void testFindByUid(){

        SqlSession session1 = SqlSessionFactoryUtil.getSession();
        UserDao userDao = session1.getMapper(UserDao.class);
        User user = userDao.findByUid(13);
        System.out.println("user=" + user);
        session1.close();

        System.out.println("---------------------------");


        SqlSession session2 =  SqlSessionFactoryUtil.getSession();
        UserDao userDao2 = session2.getMapper(UserDao.class);
        User user2 = userDao2.findByUid(13);
        System.out.println("user2=" + user2);
        session2.close();

        //删除一个
       /* SqlSession session6666 =  SqlSessionFactoryUtil.getSession();
        UserDao dao6666 = session6666.getMapper(UserDao.class);
        int row = dao6666.deleteByUid(14);
        System.out.println("row=" + row);
        session6666.commit();
        session6666.close();*/


        System.out.println("---------------------------");


        SqlSession session3 =  SqlSessionFactoryUtil.getSession();
        UserDao userDao3 = session3.getMapper(UserDao.class);
        User user3 = userDao3.findByUid(13);
        System.out.println("user3=" + user3);
        session3.close();
    }
}

注意事项

  • 只有出现了增删改的动作,才会清空二级缓存。而不能简单的以commit这个方法的调用来下结论。

  • 当我们在使用二级缓存时,缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

Mybatis延迟加载策略

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快.

懒加载只有在多表联合查询的时候才会出现,只查单张表的时候,不存在什么懒加载

使用 Assocation 实现延迟加载 (多(一)对一)

查询账户(Account)信息并且关联查询用户(User)信息。

  • 所有的懒加载查询工作,都不能使用多张表的联合查询了(内连接|外连接)

  • 必须把这些工作拆分成查单张表的工作

    1. 先查询所有的账户信息 : select * from t_account

    2. 得到所有的账户了之后, select * from t_user where uid = ?

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

Account.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
  private Integer aid;
  private Integer uid;
  private Double money;

  //表示账户属于哪一个人。
  //一个账户只能属于一个人。
  private User user ;
}

AccountDao.java

public interface AccountDao {

    //找所有的账户
    List<Account> findAll();
}

AccountDao.xml

<mapper namespace="com.zml.dao.AccountDao">
  <!--resultMap: 作用就是建立映射关系。-->
  <resultMap id="accountMap" type="account">
    <id column="aid" property="aid"/>
    <result column="money" property="money"/>
    <result column="uid" property="uid"/>
    <!--
            账户类里面有一个属性叫做User 这个user的数据必须要去查询用户表才能得到

            association: 一(多)对一的关系体现
                property: Account里面的属性user
                fetchType: lazy :表示懒加载,这个user的数据线别拿,如果真的要拿,配合下面的
                            select属性来说
                select :  指定UserDao的findByUid方法,意思就是上面如果要查询user的数据就来调用这个方法
                        写法: 包名.类名.方法名
                column:  uid , 调用findByUid方法的时候,把uid这一个列的数据传过去。

        -->
    <association property="user" fetchType="eager"
                 column="uid" select="com.zml.dao.UserDao.findByUid"/>
  </resultMap>

  <!--1. 虽然懒加载的第一步是查询第一张表的所有数据回来,即使他们的列名和属性名都一样,
        但是这里也不能使用resultType来封装数据
    2. 需要用resultMap来封装数据,只有使用了resultMap来封装数据,才有可能去牵扯出来
        用户的信息。否则现在不知道用户的信息从哪里来了
    3. 也就是第二步的查询不知道怎么走了!-->
  <select id="findAll" resultMap="accountMap">
    select * from t_account
  </select>
</mapper>

UserDao.java

public interface UserDao {

  //懒加载:一对一的第二步,根据用户的id来查询用户
  User findByUid(int uid);
}

UserDao.xml

<mapper namespace="com.zml.dao.UserDao">

    <select id="findByUid" parameterType="int" resultType="user">
        select * from t_user where uid = #{uid}
    </select>
</mapper>

测试

public class TestAccoutDao {

  //查询所有的账户,并且把与之关联的用户数据给查询出来(懒记载)
  @Test
  public void testFindAll(){

    SqlSession session = SqlSessionFactoryUtil.getSession();
    AccountDao accountDao = session.getMapper(AccountDao.class);
    List<Account> list = accountDao.findAll();
    //System.out.println("list=" + list);
    session.close();

    /*   System.out.println("-------------------------------------------");

        //关闭连接之后再看数据(查询)
        Account account = list.get(0);
        User user = account.getUser();
        System.out.println("name="+user.getUsername());*/
  }
}

Collection 实现延迟加载 (一对多,多对多)

查询所有的用户信息,并且把这个用户拥有的账户信息也查询出来

Account.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account03 {
  private Integer aid;
  private Integer uid;
  private Double money;
}

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;

  //表示一个用户有很多的账户。
  private List<Account> accountList;
}

UserDao.java

public interface UserDao {

  //懒加载的第一步,查询用户表的所有数据
  List<User> findAll();
}

UserDao.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.zml.dao.UserDao">


<resultMap id="userMap" type="user">
 <id column="uid" property="uid"/>
 <result column="username" property="username"/>
 <result column="sex" property="sex"/>
 <result column="address" property="address"/>
 <result column="birthday" property="birthday"/>

 <!--查询用户表得到的结果,只能封装成User的对象数据,没有办法封装成账户数据
     如果想要封装这个用户都有哪些账户,那么需要去查询另一张表
     其实就是指:要去调用AccountDao

         告诉mybatis,当遇到属性accountList的时候,稍微偷懒一会。
         等真的要查看它的数据的时候,再去执行查询的动作,执行AccountDao里面的findByUid方法
         顺便把这个用户的uid数据给传递过去,这样就能够查询出来这个用户都有哪些账户了。
     -->
 <collection property="accountList" fetchType="lazy"
             column="uid" select="com.zml.dao.AccountDao.findByUid"/>
</resultMap>
<!--
 懒加载的第一步: 查询第一张表的所有数据,
 并且返回的类型不能使用resultType, 必须使用resultMap-->
<select id="findAll" resultMap="userMap">
 select * from t_user
</select>

</mapper>

AccountDao.java

public interface AccountDao {

  //懒加载的第二步: 根据uid来查询有哪些账户属于这个用户。
  List<Account> findByUid(int uid);

}

AccountDao.xml

<mapper namespace="com.zml.dao.AccountDao">

<!--这里是懒加载的第二步,所以只要查询即可,然后可以使用resultType-->
<select id="findByUid" parameterType="int" resultType="account">
 select * from t_account where uid = #{uid}
</select>

</mapper>

测试类

public class TestUserDao {

  @Test
  public void testFindAll(){

    SqlSession session = SqlSessionFactoryUtil.getSession();
    UserDao userDao = session.getMapper(UserDao.class);
    //查询所有的用户
    List<User> list = userDao.findAll();
    session.close();

    //再打印list集合
    System.out.println("list=" + list);

  }
}

MyBatis注解开发

Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper映射文件了。

使用 Mybatis 注解实现基本CRUD

  • @Insert:实现新增

  • @Update:实现更新

  • @Delete:实现删除

  • @Select:实现查询

  • @SelectKey:保存之后 获得保存的id

public interface UserDao {

    //新增用户
    @Insert("insert into t_user values(null,#{username},#{sex},#{birthday},#{address})")
    int add(User user );


    //新增用户
    @SelectKey(keyProperty = "uid" , resultType = int.class , before = false, statement = "select LAST_INSERT_ID()")
    @Insert("insert into t_user values(null,#{username},#{sex},#{birthday},#{address})")
    int add02(User user );

    //删除用户
    @Delete("delete from t_user where uid = #{uid}")
    int delete(int uid);

    //根据id找用户
    @Select("select * from t_user where uid = #{uid}")
    User findByUid(int uid);

    //修改用户
    @Update("update t_user set username = #{username}, sex=#{sex}, " +
            " birthday=#{birthday},address=#{address} where uid = #{uid}")
    int update(User user);

    //查询所有
    @Select("select * from t_user")
    List<User> findAll();
}

SqlMapConfig.xml

<!-- 把当前包下的所有接口映射进行配置 -->
<mappers>
  <!-- 批量配置 -->
  <package name="com.zml.dao"></package>
</mappers>

使用Mybatis注解实现复杂关系映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现, @ResultMap 这个注解不是封装用的。

  • @Results 注解 , 代替的是标签<resultMap>

//该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results({@Result(), @Result() })或@Results(@Result())

@Resutl 注解 ,代替了 <id>标签和<result>标签

@Result(column="列名",property="属性名",one=@One(select="指定用来多表查询的 sqlmapper"),many=@Many(select=""))

@Resutl 注解属性说明	
    column 数据库的列名
    Property 需要装配的属性名
    one 需要使用的@One 注解(@Result(one=@One)()))
    many 需要使用的@Many 注解(@Result(many=@many)()))

@One 注解(一对一),代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

@Result(column="列名",property="属性名",one=@One(select="指定用来多表查询的 sqlmapper"))

@Many 注解(一对多) ,代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合

  • 注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList) 但是注解中可以不定义;

@Result(property="",column="",many=@Many(select=""))

使用注解实现(多)一对一复杂关系映射及延迟加载

  • 查询账户(Account)信息并且关联查询用户(User)信息。

  • 先查询账户(Account)信息,当我们需要用到用户(User)信息时再查询用户(User)信息。

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

Account.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
  private Integer aid;
  private Integer uid;
  private Double money;

  //表示账户和用户的关系,一个账户只能属于一个用户
  private User user;
}

AccountDao.java

public interface AccountDao {

  //查询所有的账户,并且把账户属于的用户信息也查询出来
  //使用注解实现的话,它的套路就是按照懒加载的写法来做。
  //第一步:查询所有的账户
  @Results(value={
    @Result(column = "aid" , property = "aid",id = true),
    @Result(column = "uid" , property = "uid"),
    @Result(column = "money", property = "money"),
    @Result(property = "user" , column = "uid" ,
            one = @One(fetchType = FetchType.DEFAULT, select = "com.zml.dao.UserDao.findByUid"))
  })
  @Select("select * from t_account")
  List<Account> findAll();
}

UserDao.java

public interface UserDao {

  //第二步: 根据uid来找人
  @Select("select * from t_user where uid = #{uid}")
  User findByUid(int uid);
}

测试

public class TestAccountDao {

  @Test
  public void testFindAll(){

    SqlSession session = SqlSessionFactoryUtil.getSession();
    AccountDao accountDao = session.getMapper(AccountDao.class);
    List<Account> list = accountDao.findAll();
    //System.out.println("list=" + list);
    session.close();
  }
}

使用注解实现一对多复杂关系映射及延迟加载

完成加载用户对象时,查询该用户所拥有的账户信息。

等账户信息使用的时候再查询.

Account.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
  private Integer aid;
  private Integer uid;
  private Double money;
}

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;

  //表示一个用户有很多的账户,通常使用List集合来表示
  private List<Account> accountList;
}

UserDao.java

public interface UserDao {


/**
     * 一对多的第一步: 查询所有的用户
     * 1. 这里的代码和前面的一对一的代码不差多少,结构大体相同
     * 2. 唯一要注意的地方就是现在是一对多了,所以里面必须要使用@Many注解
     * 3. @Results 其实就是我们以前xml的resultMap标签
     * 4. @Result 其实就是以前xml的 <id> 和  <result标签
     * 5. User这个类里面有一个属性(accountList)比较特殊一些, 它的数据不是从用户表来的,而是从账户表查询得到的
     *      所以需要去查询账户表
     * 6. 怎么查询账户表,调用哪个方法,要不要传递参数,就看里面怎么写。
     *      property : accountList 这个属性,它是一个集合
     *      select : 表示调用哪个方法
     *      column:表示传递什么参数过去。
     *      fetchType : 是否是懒加载,没有要求说一定要用懒加载!
     */
  @Results(value={
    @Result(column = "uid" , property = "uid" ,id = true),
    @Result(column = "username",property = "username"),
    @Result(column = "sex",property = "sex"),
    @Result(column = "birthday",property = "birthday"),
    @Result(column = "address",property = "address"),
    @Result(property ="accountList",column = "uid",
            many = @Many(fetchType = FetchType.DEFAULT,select = "com.zml.dao.AccountDao.findByUid"))
  })
  @Select("select * from t_user")
  List<User> findAll();

}

AccountDao.java

public interface AccountDao {


  //一对多查询的第二步:根据用户的id来查询账户的信息

  @Select("select * from t_account where uid = #{uid}")
  List<Account> findByUid(int uid);
}

测试

public class TestUserDao03 {
  @Test
  public void findAll(){

    SqlSession session = SqlSessionFactoryUtil.getSession();
    UserDao userDao = session.getMapper(UserDao.class);
    //查询所有的用户信息
    List<User> list = userDao.findAll();
    System.out.println("list="+list);
    session.close();
  }
}

分页处理

使用PageHelper实现分页效果

添加依赖

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>

核心配置SqlMapConfig.xml文件添加插件

<!--配置-->
<configuration>
  <!-- 引入properties文件-->
  <properties resource="db.properties"/>

  <!-- 开启redis二级缓存 默认就是开启的可以不进行设置-->
  <settings>
    <setting name="cacheEnabled" value = "true"/> 
  </settings>
  <!-- 需要在mybatis的核心配置文件中,配置分页插件。 位于`environments` 的前面。 配置分页的插件其实就是配置拦截器。这个拦截器有什么作用呢? 它的作用是能够让我们在不侵入代码|源码的情况下实现分页的效果,也就是它会在底层的sql语句给我们追加 limit ? ,?  -->
  <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
    </plugin>
  </plugins>
</configuration>

javabean

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
  private int uid;
  private String username;
  private String sex;
  private Date birthday;
  private String address;
}

dao

  • 返回值使用pagehelper提供的 Page

  • 最终的sql语句使用查询所有的语句

//测试分页
public interface UserDao04 {

  @Select("select * from t_user")
  List<User> findByPage();


  //返回的数据类型应该是 Page 而不是List集合
  @Select("select * from t_user")
  Page<User> findByPage02();
}

测试代码

  • 在查询之前需要设置查询的参数。

public class TestUserDao {

  //分页查询
  @Test
  public void testFindByPage(){
    SqlSession session = SqlSessionFactoryUtil.getSession();
    UserDao userDao = session.getMapper(UserDao.class);

    /*
            在查询之前,先设置,想要看第几页,每页想看多少条
            要想让这句话生效,必须有一个前提:拦截器必须要设置!
         */
    PageHelper.startPage(1, 2);


    //分页查询  List是接口 ,等号的右边具体是哪一种类型呢?
    //返回的类型是分页工具jar里面的一个类  Page!
    //List<User> list = userDao.findByPage();
    //System.out.println("list=" + list);

    Page<User> page = userDao.findByPage();
    System.out.println("当前是第几页:" + page.getPageNum());
    System.out.println("总共多少页:" + page.getPages());
    System.out.println("每页显示多少条:" + page.getPageSize());
    System.out.println("总共多少条:" + page.getTotal());
    System.out.println("当前这一页的集合数据:" + page.getResult());
    //虽然打印这一页的集合数据有点乱, 但是不影响它的遍历

    List<User> list = page.getResult();
    for (User user : list) {
      System.out.println("user=" + user);
    }
    session.close();
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值