MyBatis

概念

为什么会有MyBatis

JDBC缺点

  1. 存在大量的冗余代码
  2. 手动获取,关闭connection
  3. 封装ResultSet
  4. 效率不高,没有缓存

什么是MyBatis

  • MyBatis是一个持久层的ORM框架
  • 框架:是一个半成品的软件,解决了软件开发中的通用性问题,简化了开发步骤,提高效率
  • ORM:object relational mapping 对象关系映射,将程序中的对象与表记录的数据一一对应

作用

  1. 支持自定义的sql,存储过程已经高级映射
  2. 对原生的JDBC操作进行了封装,几乎消除了冗余的jdbc代码
  3. 支持xml配置sql和注解配置sql,并自动完成ORM操作,将结果返回

相关API

  • SqlSessionFactoryBuilder

    负责读取mybatis的主配置文件(MapConfig.xml),用于指定数据库的连接参数和框架参数配置

  • SqlSessionFactory

    每一个mybatis的应用程序都有一个SqlSessionFactory对象,负责创建SqlSession

  • SqlSession

    包含了所有执行sql操作的方法

    openSession(true);开启自动提交事务操作,默认false:自己管理

解析xml文件

MapConfig.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文件信息-->
    <properties resource="db.properties"></properties>
    
    <!--数据库连接信息-->
    <!--在environments可以配置多个environment,每一个environment定义一套连接配置-->
    <!--default用来指定使用哪一个environment标签,与id值对应-->
    <environments default="dev">
        <environment id="dev"><!--id唯一-->
            
            <!--type配置数据库事务管理方式,
            采用jdbc方式进行事务的提交、回滚操作
            managed依赖容器完成事务管理,本身不进行事务的提交、回滚操作-->
            <transactionManager type="jdbc"/>
            
            <!--dataSource:配置数据库连接信息,unpooled不使用连接池  pooled使用连接池-->
            <!--<dataSource type="pooled">-->    
            <dataSource type="factory.DruidDataSourceFactory"><!--使用自己创建的连接池-->
                
                <!--<property name="driver" value="${driver}"/>-->
                <property name="driverClassName" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        
    </environments>
    <!--加载映射配置文件-->
    <mappers>
        <mapper resource="SaleMapper.xml"></mapper>
        <mapper class="dao.DeptDao"></mapper>
    </mappers>
</configuration> 

Mapper.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">

<!--namespace:命名空间(全类名)		id:sql调用的statement  	resultType:指定映射的java类型-->
<mapper namespace="pojo.UserInfo">
    <!--查询-->
    <select id="findById" resultType="pojo.UserInfo">
        select * from userinfo where id=#{id}
    </select>
</mapper>

xml

MapConfig.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="db.properties"></properties>
    
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="jdbc"/>
            <dataSource type="factory.DruidDataSourceFactory">
                <property name="driverClassName" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="SaleMapper.xml"></mapper>
    </mappers>
</configuration> 

Mapper.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="pojo.UserInfo">
    <!--查询单个数据-->
    <select id="findByIdUserInfo" resultType="pojo.UserInfo">
        select * from userinfo where id=#{id}
    </select>
    
	<!--查询所有数据-->
    <select id="findAll" resultType="pojo.UserInfo">
        select * from userinfo
    </select>

    <!--新增-->
    <insert id="insert">
        insert into userinfo values (null,#{username},#{name},#{pwd},#{sex})
    </insert>

    <!--删除-->
    <delete id="delById">
        delete from userinfo where id=#{id}
    </delete>

    <!--更新-->
    <update id="update">
        update userinfo set name=#{name} where id=#{id}
    </update>
</mapper>

实体类

//	属性	get set 构造方法...

实现

//输出SqlSession	
@Test
    public void soutSession() throws IOException {
        String conf = "SqlMapConfig.xml";
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = builder.build(Resources.getResourceAsReader(conf));
        SqlSession sqlSession = build.openSession();
        System.out.println(sqlSession);
    }

	//查询单个
    @Test
    public void findById() throws IOException {
        //mybatis主配置的路径
        String conf = "SqlMapConfig.xml";
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //通过builder获取session工厂
        SqlSessionFactory factory = builder.build(Resources.getResourceAsReader(conf));
        //通过factory工厂获取session
        SqlSession session = factory.openSession();
        UserInfo userInfo = session.selectOne("findByIdUserInfo", 2);
        System.out.println(userInfo);
        //关闭会话
        session.close();
    }

	//添加
    @Test
    public void Insert() throws IOException {
        String conf = "SqlMapConfig.xml";
        //openSession(true);开启自动提交事务操作,默认false:自己管理
        SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(conf)).openSession();
        UserInfo userInfo = new UserInfo("admin", "管理员", "admin", "F");
        session.insert("insert", userInfo);
        session.commit();//提交事务
        session.close();
    }

	//删除
    @Test
    public void delById() throws IOException {
        String conf = "SqlMapConfig.xml";
        //openSession(true);开启自动提交事务操作,默认false:自己管理
        SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(conf)).openSession();
        session.insert("delById", 6);
        session.commit();//提交事务
        session.close();
    }

	//更新
    @Test
    public void update() throws IOException {
        String conf = "SqlMapConfig.xml";
        SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(conf)).openSession();
        UserInfo userInfo = new UserInfo();
        userInfo.setId(7);
        userInfo.setName("admin");
        session.update("update", userInfo);
        session.commit();//提交事务
        session.close();
    }

	//查询所有
    @Test
    public void findAll() throws IOException {
        String conf = "SqlMapConfig.xml";
        SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(conf)).openSession();
        List<Object> list = session.selectList("findAll");
        session.close();
        System.out.println(list);
    }
}

接口+注解

接口

public interface DeptDao {
    @Select("select * from dept where id=#{id}")
    Dept findById(Integer id);
}

MapConfig.xml

<mapper class="dao.DeptDao"></mapper>

实现

    @Test
    public void findById() throws IOException {
        String conf = "SqlMapConfig.xml";
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(conf)).openSession();
        //getMapper代理对象
        DeptDao dao = sqlSession.getMapper(DeptDao.class);
        Dept dept = dao.findById(1);
        System.out.println(dept);
        sqlSession.close();
    }

接口+XML

接口

Dept findById(Integer id);

DeptMapper

<?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">
<!--namespace:命名空间  resultType:指定映射的java类型-->
<mapper namespace="dao.DeptDao">
    <!--查询-->
    <select id="findById" resultType="pojo.Dept">
        select * from dept where id=#{id}
    </select>
</mapper>

MapConfig.xml

<mapper resource="DeptMapper.xml"></mapper>

优化

db.properties

<!--引入文件-->
<properties resource="db.properties"></properties>
<!--使用参数-->
<property name="username" value="${username}"/>

连接池使用Druid

  1. 定义连接池工厂,继承UnpooledDataSourceFactory
  2. 提供构造方法,在该方法中,给父类的dataSource赋值
  3. 在mybatis的主配置文件中,通过type指向自定义的dataSourceFactory
public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    public DruidDataSourceFactory() {
        this.dataSource = new DruidDataSource();
    }
}
<dataSource type="factory.DruidDataSourceFactory">
	<property name="driverClassName" value="${driver}"/>

封装SqlSession

封装SqlSession,回去session(同一个线程中获取到的是同一个session对象),事务提交、回滚,关闭session

package util;

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 java.io.IOException;

/**
 * 获取session 关闭session  提交事务  回滚事务
 * 同一个线程获取到的是同一个session
 */
public class SessionUtil {
    private static SqlSessionFactory factory;
    /**
     * 创建threadLocal对象,存储的数据是当前线程的sqlSession对象
     */
    private static ThreadLocal<SqlSession> tl = new ThreadLocal<SqlSession>();

    static {
        try {
            //同一个应用,只需要一个factory,所以放在static语句块中执行一次即可
            factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("SqlMapConfig.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取session
     *
     * @return
     */
    public static SqlSession getSession() {
        //先从treadLocal中获取当前线程对应的session对象
        SqlSession session = tl.get();
        //如果session为空,则创建新的session
        if (session == null) {
            session = factory.openSession();
            //将session保存到threadLocal中
            tl.set(session);
        }
        return session;
    }

    /**
     * 释放session
     */
    public static void closeSession() {
        SqlSession session = tl.get();
        if (session != null) {
            session.close();
            tl.remove();
        }
    }

    /**
     * 提交事务
     */
    public static void commit() {
        SqlSession session = tl.get();
        if (session != null) {
            session.commit();
            closeSession();
        }
    }

    /**
     * 回滚事务
     */
    public static void rollback() {
        SqlSession session = tl.get();
        if (session != null) {
            session.rollback();
            closeSession();
        }
    }

    public static void main(String[] args) {
        SqlSession session1 = getSession();
        SqlSession session2 = getSession();
        System.out.println(session1 == session2);
        System.out.println(session2);
        
        Thread t = new Thread(() -> {
            SqlSession session3 = getSession();
            System.out.println(session3);
        });
        t.start();
    }
}

配置实体类别名

MapConfig.xml配置

配置单个实体类
    <typeAliases>
        <!--type:数据返回类型		alias:别名-->
        <typeAlias type="pojo.Dept" alias="dept"></typeAlias>
    </typeAliases>
配置多个实体类

使用扫包的形式批量给实体类定义别名,默认是类名的小写或者大写

    <typeAliases>
     	<!--name:包的地址-->
        <package name="pojo"/>
    </typeAliases>

注解配置

@Alias("dept1")
public class Dept{}

实体类属性名和字段不一致

sql别名

    <select id="findById" resultType="dept1">
            select id,deptno,name as dname,location from dept where id=#{id}
    </select>

resultType=“map”

    <select id="findById" resultType="map">
        select * from dept where id=#{id}
    </select>

resultMap

自定义resultMap,指定字段名与属性名的对应关系

    <select id="findById" resultMap="deptMap">
            select * from dept where id=#{id}
    </select>

    <resultMap id="deptMap" type="dept1">
        <!--property:对象的属性名   column:表的字段名-->
        <id property="id" column="id"></id>
        <result property="deptno" column="deptno"></result>
        <result property="dname" column="name"></result>
        <result property="location" column="location"></result>
    </resultMap>

参数传递

一个参数

无要求,任意书写

select * from sale where id=#{id}

多个参数

参数序号

使用arg0、arg1…或者param1、param2…替代sql中的参数

select * from dept limit #{arg0},#{arg1}

select * from dept limit #{param1},#{param2}

注解

基于注解配置参数,适合参数较少的情况

//接口
Map<String, Object> findByPage(@Param("pageIndex") Integer pageIndex, @Param("pageSize") Integer pageSize);

JavaBean

将参数封装到JavaBean中,在sql中引用对象的属性

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Page implements Serializable {
    private Integer pageIndex;
    private Integer pageSize;
}
//接口
Map<String, Object> findByPage(Page page);
Page page = new Page(1, 1);
Map<String, Object> map = dao.findByPage(page);

Map

将参数封装到map中,#{key}

//接口
Map<String, Object> findByPage(Map<String,Integer> map);
Map<String, Integer> mapInfo = new HashMap<>();
mapInfo.put("pageIndex", 1);
mapInfo.put("pageSize", 1);
Map<String, Object> map = dao.findByPage(mapInfo);

模糊查询

//接口
List<Dept> findByName(String dname);
<select id="findByName" resultType="dept1">
	select * from dept where dname like concat('%',#{dname},'%')
</select>
List<Dept> list = SessionUtil.getSession().getMapper(DeptDao.class).findByName("研发");

主键回填

useGeneratedKeys

  • useGeneratedKeys:使用主键回填,默认false
  • keyProperty:自动生成的主键值回填给哪一个属性
//接口
Integer insert(Dept dept);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
	insert into dept  values (null,#{deptno},#{dname},#{location});
</insert>
Dept dept = new Dept(10, "111", "111");
SessionUtil.getSession().getMapper(DeptDao.class).insert(dept);
SessionUtil.commit();
System.out.println(dept.getId());

last_insert_id

  • last_insert_id:最后一条记录的id值

  • order

    ​ after:先执行insert再查询id,适用于MySQL,支持自动递增的数据库

    ​ before:先生成id,再执行insert,适用不支持自动自增的数据库,比如Oracle:select uuid()

  • resultType:回填值的数据类型

  • keyProperty:需要回填的字段

    <insert id="insert">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id()
        </selectKey>
        insert into dept values (null,#{deptno},#{dname},#{location});
    </insert>

log4j

log4j是一个日志记录的工具,可以根据信息的级别按照不同的方式和格式输出。

组成

  • 日志器(logger):负责控制消息的输出,提高各种不同级别的输出方法
  • 输出器(Appender):负责消息输出的方式,比如输出到控制台还是输出到文件
  • 布局器(layout):负责消息输出的格式,比如级别-消息

步骤

  1. 引入log4j.jar
  2. 在resources目录下定义log4j.properties文件,在该消息中定义消息输出的级别,采用哪种输出器和哪种布局器
  3. 代码中使用日志器输出消息

实现

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
#logger 日志器:控制消息的级别,并指定消息输出的目的地
log4j.rootLogger=error, myconsole,myfile
#appender 输出器:消息输出的方式
log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
log4j.appender.myfile=org.apache.log4j.FileAppender
log4j.appender.myfile.File=F:\\idealogger\\mylog.txt
#layout 布局器:消息以什么格式输出
log4j.appender.myconsole.layout=org.apache.log4j.TTCCLayout
log4j.appender.myfile.layout=org.apache.log4j.PatternLayout
log4j.appender.myfile.layout.ConversionPatten=[%t] %5p -%m%n
    @Test
    public void test() {
        Logger logger = Logger.getLogger(TestLog4j.class);
        logger.debug("调试信息");
        logger.info("普通信息");
        logger.warn("警告信息");
        logger.error("错误信息");
        logger.fatal("致命信息");
    }

        //List<Emp> emps = mapper.findByDeptno(condition);
        //logger.info("根据部门编号查询员工emp:"+emps);

全局设置

  1. 依赖

  2. 添加mybatis-config.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>
        <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
        <typeAliases>
            <package name="entity"/>
        </typeAliases>
    </configuration>
    
  3. 在spring配置文件SqlSessionFactoryBean中引入

    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    

动态sql

常用元素

  • 判断元素:if choose
  • 关键字元素:where set trim
  • 循环元素:forEach

备用:作为查询的条件参数

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Condition implements Serializable {
    private Integer deptno;
    private Double salary;

    public Condition(Integer deptno) {
        this.deptno = deptno;
    }
    public Condition(Double salary) {
        this.salary = salary;
    }
}

if

语法

<select>
sql语句
  <if test="条件表达式">sql语句2</if>
</select>

实现

//接口
List<Emp> findById(Condition condition);
//测试
SqlSession session = SessionUtil.getSession();
EmpDao empDao = session.getMapper(EmpDao.class);
Condition con = new Condition(40);
List<Emp> list = empDao.findById(con);
logger.info("信息:" + list);
SessionUtil.closeSession();

Choose

语法

<select>
    sql语句1
    <choose>
        <when test="条件表达式">sql语句2</when>
        <othwise>sql语句3</otherwise>
    </choose>
</select>

实现

//接口
List<Emp> findBySal(Condition condition);
    <select id="findBySal" resultType="emp">
        select * from emp
        <choose>
            <when test="salary >3000">
                where salary>#{salary}
            </when>
            <otherwise>where salary>3000</otherwise>
        </choose>
    </select>
        Condition condition = new Condition(8000);
        List<Emp> list = empDao.findBySal(condition);
        logger.info("信息:" + list);

where

语法

<select>
    sql语句1
    <where>
    	动态条件追加
    </where>
</select>

实现

//接口
List<Emp> findByDeptnoAndSal(Condition con);
    <select id="findByDeptnoAndSal" resultType="emp">
        select * from emp
        <where>
            <if test="deptno!=null">deptno=#{deptno}</if>
            <choose>
                <when test="salary >3000">and salary>#{salary}</when>
                <otherwise>and salary>3000</otherwise>
            </choose>
        </where>
    </select>
        Condition condition = new Condition(10, (double) 4000);
        List<Emp> list = empDao.findByDeptnoAndSal(condition);

set

语法

<update>
    sql语句1
    <set>动态追加需要修改的字段</set>
</update>

实现

//接口
void updateEmp(Condition condition);
    <update id="updateEmp">
        update emp
        <set>
            <if test="ename!=null">ename=#{ename},</if>
            <if test="salary!=null">salary=#{salary}</if>
        </set>
        where deptno=#{deptno}
    </update>
        Condition condition = new Condition(10, (double) 8000, "张三丰");
        empDao.updateEmp(condition);

trim

语法

  • prefix:在自己包含的内容前面加上某个前缀

  • suffix:在内容后面添加后缀

  • prefixOverrides:内容首部某些数据可以过滤

  • suffixOverrides:内容尾部某些数据可以过滤

    <trim prefix="where" prefixOverrides="and|or"></trim>
    <trim prefix="set" suffixOverrides=","></trim>
    

实现

    List<Emp> findByDeptnoAndSal(Condition con);
    void updateEmp(Condition condition);
    <select id="findByTrim" resultType="emp">
        select * from emp
        <trim prefix="where" prefixOverrides="and">
            <if test="deptno!=null">deptno=#{deptno}</if>
            <choose>
                <when test="salary >3000">and salary>#{salary}</when>
                <otherwise>and salary>3000</otherwise>
            </choose>
        </trim>
    </select>


    <update id="updateByTrim">
        update emp
        <trim prefix="set" suffixOverrides=",">
            <if test="ename!=null">ename=#{ename},</if>
            <if test="salary!=null">salary=#{salary}</if>
        </trim>
        where deptno=#{deptno}
    </update>
        Condition condition = new Condition(10, (double) 4000);
        List<Emp> list = empDao.findByDeptnoAndSal(condition);
        
        Condition condition = new Condition(50, (double) 8000, "张三丰");
        empDao.updateEmp(condition);

forEach

语法

<select>
    sql语句1
    <forEach collection="集合" item="迭代变量" separator="," open="(" close=")">
        #{迭代变量}
    </forEach>
</select>

实现

    List<Emp> findByEmpno(Condition con);
    <select id="findByEmpno" resultType="emp">
        select * from emp where empno in
        <foreach collection="empnos" item="empno" separator="," open="(" close=")">#{empno}</foreach>
    </select>
        Condition condition = new Condition();
        condition.setEmpnos(Arrays.asList(1001, 1008, 1011));
        List<Emp> list = empDao.findByEmpno(condition);

关联映射

嵌套查询

通过查询另一个sql语句来返回关联的数据结果

根据员工id查询员工以及该员工的部门信息

    <!--id:与接口名一致,调用的查询语句       resultMap:返回值标识-->
    <select id="findByEmpno1" resultMap="empMap">
        select * from emp where empno=#{empno}  /*主表:根据员工id查询该员工的所有信息*/
    </select>
    <!--id:副表的查询标识       resultMap:返回值类型-->
    <select id="findDept" resultType="dept">
        select * from dept where deptno=#{deptno}   /*副表:根据该员工的部门编号查询所在部门信息*/
    </select>
    <!--resultMap:主表的返回值标识     type:主表的返回值类型-->
    <resultMap id="empMap" type="emp">
        <!--column:字段名     property:属性名-->
        <id column="id" property="id"></id>
        <result column="deptno" property="deptno"></result>

        <!--association:关联    property:副表(副标的结果)   select:副表的查询标识
        column:副表查询的条件参数   javaType:副表的返回值类型-->
        <association property="dept" select="findDept" column="deptno" javaType="dept"></association>
    </resultMap>
//接口
List<Emp> findByEmpno1(Integer id);
        EmpDao dao = session.getMapper(EmpDao.class);
        List<Emp> list = dao.findByEmpno1(1002);

嵌套结果

    <!--id:与接口名一致,调用的查询语句       resultMap:返回值标识-->
    <select id="findByEmpno2" resultMap="empMap2">
        /*emp主表,dept副表*/
        select e.id,e.empno,e.ename,e.position,e.salary,e.bonus,e.hiredate,e.leader,e.deptno,
        d.id as did,d.deptno,d.dname,d.location from emp e join dept d
        on e.deptno=d.deptno where e.empno=#{empno}
    </select>

    <!--id:返回值标识  type:主表的返回类型-->
    <resultMap id="empMap2" type="emp">
        <!--column:主表的字段    property:属性名-->
        <id column="id" property="id"></id>
        <result column="empno" property="empno"></result>
        <result column="ename" property="ename"></result>
        <result column="position" property="position"></result>
        <result column="salary" property="salary"></result>
        <result column="bonus" property="bonus"></result>
        <result column="hiredate" property="hiredate"></result>
        <result column="leader" property="leader"></result>
        <result column="deptno" property="deptno"></result>
        
        <!--association:关联      property:副表(副标的结果)  javaType:副表的返回值类型-->
        <association property="dept" javaType="dept">
            <!--column:副表的字段    property:属性名-->
            <id column="did" property="id"></id>
            <result column="dname" property="dname"></result>
            <result column="deptno" property="deptno"></result>
            <result column="location" property="location"></result>
        </association>
    </resultMap>
//接口和实现与嵌套查询一致

集合映射

嵌套查询

查询编号是1的部门以及该部门下的员工
    <!--id:主表的查询标识,与接口同名(调用)   resultMap:主表的返回值标识 -->
    <select id="findById2" resultMap="deptMap2">
        select * from dept where id=#{id}   /*主表查询语句*/
    </select>
    <!--id:副表的查询标识   resultType:副表的返回值类型 -->
    <select id="findEmps" resultType="emp">
        select * from emp where deptno=#{deptno}    /*副表的查询语句*/
    </select>
    <!--id:主表的查询标识  type:主表的返回类型-->
    <resultMap id="deptMap2" type="dept">
        <!--column:字段名  property:属性-->
        <id column="id" property="id"></id>
        <result column="deptno" property="deptno"></result>
        <!--collection:集合   select:副表的查询标识  column:副表的查询条件参数
                javaType:返回值类型(用来指定对象类型)
                ofType:泛型(映射到list集合中pojo的类型)-->
        <collection property="emps" select="findEmps" column="deptno"
                    javaType="arrayList" ofType="emp"></collection>
    </resultMap>
//接口
Dept findById2(Integer id);
        DeptDao dao = session.getMapper(DeptDao.class);
        Dept dept = dao.findById2(1);

嵌套结果

    <!--id:主表的查询标识     resultMap:返回值标识-->
    <select id="findById3" resultMap="deptMap3">
    /*dept主表    emp副表*/
        select e.* ,d.id did ,d.deptno,d.dname,d.location from emp join dept d
        on d.deptno=e.deptno where did=#{id}
    </select>
    <!--id:返回值标识    type:返回值类型-->
    <resultMap id="deptMap3" type="dept">
        <!--主表     column:字段名  property:属性-->
        <id column="did" property="id"></id>
        <collection property="emps" ofType="emp">
            <!--副表      column:字段名  property:属性-->
            <id column="id" property="id"></id>
        </collection>
    </resultMap>
//接口和实现与嵌套查询一致

延迟加载

条件

只有嵌套查询才会设计延迟加载,默认是直接加载

分类

  • 直接加载:执行完主加载对象的sql之后,立即执行关联对象的select语句(副表运行
  • 延迟加载:执行完主加载对象的sql之后,不会立即执行关联对象的select语句,当需要查询关联对象的信息时,才会触发关联对象的select语句(当需要时副表才运行

配置

统一配置

MapConfig.xml

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

单一配置

修改某一个sql的加载时机:fetchType=“eager(立即加载)/lazy(延迟加载)”

<association property="dept" javaType="dept" fetchType="eager">

缓存

简介

什么是缓存

  • 存在内存中的临时数据
  • 将用户经常使用的数据放到内存中(缓存),用户需要时可以直接从内存中获取,不需要磁盘的读写,从而提高效率,处理高并发系统的性能问题

为什么使用缓存

减少和数据库的交互(存盘读写相对于内存很慢),减少系统的开销,提高系统效率

什么样的数据能使用缓存

经常查询并且不经常发送改变的数据

分类

mybatis系统中定义了两种缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启(SqlSession级别缓存,也成为本地缓存)
  • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存(SqlSessionFactory)

一级缓存

定义

一级缓存为每一个SqlSession对象单独分配内存,多个SqlSession内存不共享,该缓存无需手动开启,直接使用

特点

  1. 多次查询如果是同一个selsession对象,则第一次查询之后的数据会放到缓存中,后续的查询直接访问缓存中的数据
  2. 第一次查询后,如果对查询出的对象进行了修改(修改会影响缓存数据),第二次查询直接访问的是缓存数据,会导致查询结果与数据库不一致的结果
  3. 第一次查询后,使用当前的sqlsession进行DML操作,会导致缓存的数据失效

演示

        SqlSession session = SessionUtil.getSession();
        List<Dept> list = session.getMapper(DeptDao.class).findById(1);
        Logger logger = Logger.getLogger(DeptTest.class);
        logger.info("第一次查询:" + list);

        List<Dept> list1 = session.getMapper(DeptDao.class).findById(1);
        logger.info("第二次查询:" + list1);
        SessionUtil.closeSession();

在这里插入图片描述

缓存失效情况

  • 一级缓存是SqlSession级别的缓存,是一直开启的,我们无法关闭
  • 一级缓存失效情况:没有使用到当前的一级缓存,效果就是还需要再向数据库发起一起查询请求
  1. SqlSession不同:每一个SqlSession的缓存是独立的

            SqlSession session = SessionUtil.getSession();
            List<Dept> list = session.getMapper(DeptDao.class).findById(1);
            logger.info("第一次查询:" + list);
            SessionUtil.closeSession();
    
            SqlSession session1 = SessionUtil.getSession();
            List<Dept> list1 = session1.getMapper(DeptDao.class).findById(1);
            logger.info("第二次查询:" + list1);
            SessionUtil.closeSession();
    

  2. SqlSession相同,查询条件不同:当前缓存中不存在该数据

            SqlSession session = SessionUtil.getSession();
            DeptDao dao = session.getMapper(DeptDao.class);
            List<Dept> list = dao.findById(1);
            logger.info("第一次查询:" + list);
    
            List<Dept> list1 = dao.findByName("研发");
            logger.info("第二次查询:" + list1);
            SessionUtil.closeSession();
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zziLCF6-1655206253665)(image-20211206210323501.png)]

  3. SqlSession相同,两次查询中间执行了DML操作:缓存数据可能发生了变化

            SqlSession session = SessionUtil.getSession();
            DeptDao dao = session.getMapper(DeptDao.class);
            List<Dept> list = dao.findById(1);
            logger.info("第一次查询:" + list);
    
            dao.update(new Dept(1, "北京"));
            session.commit();
    
            List<Dept> list1 = dao.findByName("研发");
            logger.info("第二次查询:" + list1);
            SessionUtil.closeSession();
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vpo3E9fd-1655206253666)(image-20211206211203285.png)]

    SelSession相同,手动清除一级缓存

            SqlSession session = SessionUtil.getSession();
            DeptDao dao = session.getMapper(DeptDao.class);
            List<Dept> list = dao.findById(1);
            logger.info("第一次查询:" + list);
    
            session.clearCache();//手动清除缓存
    
            List<Dept> list1 = dao.findById(1);
            logger.info("第二次查询:" + list1);
            SessionUtil.closeSession();
    

二级缓存

概念

  • 二级缓存也叫全局缓存,一级缓存作用域低,导致二级缓存的诞生

分页实现

常用方式

  1. 使用 sql 的 limit 多参传递方式实现
  2. RowBounds
  3. PageHelper

limit

实现步骤看 参数传递–多个参数

RowBounds

//接口
List<UserInfo> findByRowBounds();
<select id="findByRowBounds" resultType="dept">
    select * from dept
</select>
int currentPage = 2;//第几页
int pageSize = 2;//每页显示几个
RowBounds rowBounds = new RowBounds((currentPage - 1) * pageSize, pageSize);
List<UserInfo> list = session.selectList("findByRowBounds", null, rowBounds);

PageHelper

官方文档

  1. 引入pageHelper依赖:pom.xml

            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper</artifactId>
                <version>5.2.0</version>
            </dependency>
    
  2. 在MapConfig.xml中添加分页插件的配置

        <plugins>
            <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
        </plugins>
    
  3. 在mapper中编写sql

        <select id="findByPageHelper" resultType="dept">
            select  * from dept
        </select>
    
  4. 定义接口

    List<Dept> findByPageHelper();
    
  5. 实现

            SqlSession session = SessionUtil.getSession();
            DeptDao dao = session.getMapper(DeptDao.class);
            //页码与每页显示记录数的设置
            //最终分页的数据会保存到当前线程的ThreadLocal中,便于分页的拦截器获取分页的相关参数
            PageHelper.startPage(2, 2);
            //真正执行sql前,会调用拦截器,拦截器从thread local中获取分页的参数,并且拼接到sql中
            //查询结束之后,会清空thread local中的数据
            List<Dept> list = dao.findByPageHelper();
            logger.info("分页:" + list);
            logger.info("######################");
            PageInfo<Dept> pageInfo = new PageInfo<>(list);
            logger.info("分页查询pageInfo:" + pageInfo);
            SessionUtil.closeSession();
    

    在这里插入图片描述

注意

  1. 只有在PageHelper.startPage()方法之后的第一个查询会执行分页
  2. 分页插件不支持嵌套结果查询

逆向工程

导入依赖:pom

<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
</dependency>

添加插件配置:pom

 <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <configurationFile>
                        src/main/resources/generatorConfig.xml
                    </configurationFile>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>

generatorConfig.xml

  1. 修改mysql-connector-java-5.1.47.jar位置
  2. 修改数据库连接信息
  3. 修改entity、mapper包的位置
  4. 修改需要生成的表-实体类
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包-->
    <classPathEntry location="F:\Tools_JAR\mysql5.1\mysql-connector-java-5.1.47.jar"/>
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!-- JavaBean 实现 序列化 接口 -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库链接URL,用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/db05?useUnicode=true" userId="root" password="123456">
        </jdbcConnection>
        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true 时把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal-->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 生成模型的包名和位置-->
        <javaModelGenerator targetPackage="entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成映射文件的包名和位置-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成mapper.java的包名和位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名-->
        <table tableName="user" domainObjectName="User"></table>
    </context>
</generatorConfiguration>

实现

        SqlSession session = SessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.selectByPrimaryKey(2);	//接口的中方法:规则
        Logger logger = Logger.getLogger(UserTest.class);
        logger.info("查询出来的数据:" + user);
        SessionUtil.closeSession();

原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kRgCIBFz-1655206253669)(mybatis流程 (2)].jpg)

批量操作

新增

mapper

int insertAll(List<User> list);

mapper.xml

    <insert id="insertAll" parameterType="java.util.List" useGeneratedKeys="false">
        insert into user
        (name,email,phone,sex,province,city,datainfo,staticinfo)
        values
        <foreach collection="list" item="u" index="index" separator=",">
            (
            #{u.name},
            #{u.email},
            #{u.phone},
            #{u.sex},
            #{u.province},
            #{u.city},
            now(),
            '1'
            )
        </foreach>
    </insert>

service

    //批量添加
    int insertAll(List<User> list);

impl

    @Override
    public int insertAll(List<User> list) {
        return mapper.insertAll(list);
    }

controller

    @ApiOperation("导入数据")
    @PostMapping("upload")
    @ResponseBody
    public void upload(@RequestParam("file") MultipartFile file) {
        try {
            synchronized (this) {
                //添加到mysql的数据
                ArrayList<User> insertList = new ArrayList<>();

                //excel的数据
                List<User> list = ExcelUtils.importExcel(file, User.class);

                //mysql数据库中所有的邮箱信息
                List<String> listEmail = userService.findAllEmail();

                Jedis jedis = new Jedis("localhost");
                //将邮箱信息添加到redis中
                for (String s : listEmail) {
                    jedis.sadd("listInfo", s);
                }
                //循环遍历是否存在
                for (User user : list) {
                    if (jedis.sismember("listInfo", user.getEmail()) == false) {
                        insertList.add(user);
                    }
                }
                //获取到redis所有数据,遍历删除(清空redis)
                Set<String> smembers = jedis.smembers("listInfo");
                for (String smember : smembers) {
                    jedis.srem("listInfo", smember);
                }
                //将未重复的数据添加到mysql中
                if (insertList.size() != 0) {
                    userService.insertAll(insertList);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

删除

mapper

int deleteMany(List list);

mapper.xml

    <delete id="deleteMany" parameterType="java.util.List">
        delete from user where id in
        <foreach collection="list" item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </delete>

service

    //批量删除
    int deleteMany(List list);

impl

    @Override
    public int deleteMany(List list) {
        return mapper.deleteMany(list);
    }

controller

    @ApiOperation("删除多个数据")
    @DeleteMapping("delMany")
    @ResponseBody
    public ResultJson delMany(@RequestBody List<String> ids) {
        int i = userService.deleteMany(ids);
        if (i < 0) {
            return new ResultJson(null, ResultCode.FAIL);
        }
        return new ResultJson(i, ResultCode.SUCCESS);
    }

查询

mapper

    List<User> findMany(@Param("ids") List list);

mapper.xml

    <select id="findMany" resultType="com.night.entity.po.User">
        select * from user where id in
        <foreach collection="ids" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </select>

service

List<User> findMany(@Param("ids") List list);

impl

    @Override
    public List<User> findMany(List list) {
        return mapper.findMany(list);
    }

controller

    @PostMapping("exportdata")
    @ResponseBody
    public ResultJson test(@RequestBody List<String> ids) {
        String keymsg = UUID.randomUUID().toString();
        try {
            List<User> list = userService.findMany(ids);
            map.put(keymsg, list);
        } catch (Exception e) {
            return null;
        }
        return new ResultJson(keymsg, ResultCode.SUCCESS);
    }

js

        function dataexport() {
            var ids = [];
            $("input[name='cbele']:checked").each(function (i) {
                ids.push($(this).val())
                $("body").append("<p>" + $(this).val() + "</p>")
            })
            console.log(ids)
            if (confirm("确定导出数据吗?")) {
                $.ajax({
                    type: "post",
                    dataType: "json",
                    url: "/user/exportdata",
                    contentType: "application/json",
                    data: JSON.stringify(ids),
                    success: function (res) {
                        console.log(res.data)
                        window.location.href = "/user/downlist?key=" + res.data;
                    },
                    error: function (res) {
                        console.log(res)
                    }
                });
            }
        }

html

 <a href="javascript:;" onclick="dataexport()" class="btn btn-warning radius">

模糊查询

mapper

List<User> findLike(String str, String qtime, String htime);

mapper.xml

    <select id="findLike" resultType="com.night.entity.po.User">
        select * from user
        <where>
            <if test="qtime!=''">
                and 0!= ${qtime}
            </if>
            <if test="qtime!=null and qtime.trim.length>0">
                and dataInfo &gt;='${qtime}'
            </if>
            <if test="htime!=null and htime.trim.length>0">
                and dataInfo &lt;='${htime}'
            </if>
            <if test="str !=null and str.trim().length() > 0">
                and (name like '%${str}%' or email like '%${str}%' or phone like '%${str}%')
            </if>
        </where>
    </select>

service

    //模糊查询
    List<User> findLike(String str, String qtime, String htime);

impl

    @Override
    public List<User> findLike(String str, String qtime, String htime) {
        return mapper.findLike(str, qtime, htime);
    }

controller

    @ApiOperation("模糊查询")
    @GetMapping("findLike")
    public String findLike(String str, String qtime, String htime, Model model) {
        model.addAttribute("userList", userService.findLike(str, qtime, htime));
        return "memberlist";
    }

html

 <form action="/user/findLike" method="get">

修改

   @ApiOperation("修改数据")
    @PatchMapping("update")
    public String update(User user) {
        userService.updateByPrimaryKeySelective(user);
        return "redirect:/user/findAll";
    }

封装结果

ResultCode

package com.night.util.result;

/**
 * @Author:night_du
 * @Date:2021/12/30 15:24
 */
public enum ResultCode {
    //##########TODO 请求成功 2**
    /**
     * 成功
     */
    SUCCESS("2000", "成功"),

    /**
     * 操作失败
     */
    FAIL("2001", "操作失败"),

    /**
     * 数据已存在
     */
    SUCCESS_IS_HAVE("2002", "数据已存在"),
    /**
     * 没有结果
     */
    NOT_DATA("2003", "没有结果"),

    //##########TODO 客户端错误 4**
    /**
     * 没有登录
     */
    NOT_LOGIN("4000", "没有登录"),

    /**
     * 发生异常
     */
    EXCEPTION("4001", "发生异常"),

    /**
     * 系统错误
     */
    SYS_ERROR("4002", "系统错误"),

    /**
     * 参数错误
     */
    PARAMS_ERROR("4003", "参数错误 "),

    /**
     * 不支持或已经废弃
     */
    NOT_SUPPORTED("4004", "不支持或已经废弃"),

    /**
     * AuthCode错误
     */
    INVALID_AUTHCODE("4005", "无效的AuthCode"),

    /**
     * 太频繁的调用
     */
    TOO_FREQUENT("4006", "太频繁的调用"),

    /**
     * 未知的错误
     */
    UNKNOWN_ERROR("4007", "未知错误"),

    /**
     * 未设置方法
     */
    NOT_METHOD("4008", "未设置方法");

    //##########TODO 服务器错误 5**

    private ResultCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 对应状态码
     */
    private String code;
    /**
     * 返回内容
     */
    private String msg;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

ResultJson

package com.night.util.result;

import lombok.Data;

/**
 * @Author:night_du
 * @Date:2021/12/30 15:22
 */
@Data
public class ResultJson<T> {

    private String code;//状态码
    private String msg;//提示消息
    private T data;//数据

    //成功
    public ResultJson(T t) {
        this.setCode(ResultCode.SUCCESS.getCode());
        this.setMsg(ResultCode.SUCCESS.getMsg());
        this.setData(t);
    }

    /**
     * 已有的ResultCode 进行返回
     */
    public ResultJson(T t, ResultCode code) {
        this.setCode(code.getCode());
        this.setMsg(code.getMsg());
        this.setData(t);
    }

    /**
     * 完全自定义返回
     */
    public ResultJson(T t, String code, String message) {
        this.setCode(code);
        this.setMsg(message);
        this.setData(t);
    }
}

    EXCEPTION("4001", "发生异常"),

    /**
     * 系统错误
     */
    SYS_ERROR("4002", "系统错误"),

    /**
     * 参数错误
     */
    PARAMS_ERROR("4003", "参数错误 "),

    /**
     * 不支持或已经废弃
     */
    NOT_SUPPORTED("4004", "不支持或已经废弃"),

    /**
     * AuthCode错误
     */
    INVALID_AUTHCODE("4005", "无效的AuthCode"),

    /**
     * 太频繁的调用
     */
    TOO_FREQUENT("4006", "太频繁的调用"),

    /**
     * 未知的错误
     */
    UNKNOWN_ERROR("4007", "未知错误"),

    /**
     * 未设置方法
     */
    NOT_METHOD("4008", "未设置方法");

    //##########TODO 服务器错误 5**

    private ResultCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 对应状态码
     */
    private String code;
    /**
     * 返回内容
     */
    private String msg;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

ResultJson

package com.night.util.result;

import lombok.Data;

/**
 * @Author:night_du
 * @Date:2021/12/30 15:22
 */
@Data
public class ResultJson<T> {

    private String code;//状态码
    private String msg;//提示消息
    private T data;//数据

    //成功
    public ResultJson(T t) {
        this.setCode(ResultCode.SUCCESS.getCode());
        this.setMsg(ResultCode.SUCCESS.getMsg());
        this.setData(t);
    }

    /**
     * 已有的ResultCode 进行返回
     */
    public ResultJson(T t, ResultCode code) {
        this.setCode(code.getCode());
        this.setMsg(code.getMsg());
        this.setData(t);
    }

    /**
     * 完全自定义返回
     */
    public ResultJson(T t, String code, String message) {
        this.setCode(code);
        this.setMsg(message);
        this.setData(t);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值