MyBatis

一:概述

参考: 官方文档

1、什么是Mybatis?

官网说明:

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

也就是:

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

数据持久化(是个动作)

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程。
  • 内存特点:断电即失,成本大。
  • 实现:数据库、io文件持久化

为什么需要持久化?

  • 重要数据不能丢失
  • 内存太贵了

持久层(是个名词)

  • Dao层
  • 完成持久化工作的代码块
  • 层:界限明显

2、发展历史

百度百科:

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。2013年11月迁移到Github

iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)

3、如何获得Mybatis?

  • maven仓库:

    <dependency>
    	<groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    
  • Github:http://github.com/mybatis/mybatis-3/releases

4、Mybatis特点

百度百科:

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • **提供xml标签,**支持编写动态sql。 [2]

总结:

  • 传统的JDBC代码很复杂,Mybatis整合了过程,方便我们使用,达到自动化的效果,我们只需调用功能就行。
  • 功能:将数据存入到数据库中。

二:入门使用

1、基本配置流程(常用的情况)

  1. 相关依赖(maven配置)

    <!-- Mybatis依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>x.x.x</version>
    </dependency>
    
    <!-- mysql驱动 -->
    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<version>8.0.19</version>
    </dependency>
    
  2. 配置mybatis-config.xml。

    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
        <settings>
            <!-- 设置mybatis输出日志 -->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
    
        <environments default="mybatis">
            <environment id="mybatis">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url"
                              value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;severTimezone=GMT"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="com/qiang/dao/UserDao.xml"/>
        </mappers>
    </configuration>
    
  3. 编写工具类

    /**
     * 根据官方文档,写个工具类,实现 sqlSessionFactory --> sqlSession
     */
    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
        // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
        public static SqlSession getSqlSession() {
            return sqlSessionFactory.openSession();
        }
    }
    
    

2、编写代码

User是一个JavaBean,这里就不写了。

Dao层

Dao层只需要编写接口(Java类)和Mapper(xml文件)。也就是说原来接口的实现类现在用对应的mapper.xml文件代替。

而接口与mapper文件的关联在于 namespace!,而且,每一个Mapper.xml都需要在Mybatis核心配置文件中注册!

如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOttf3cQ-1628078376556)(D:\笔记\笔记(work)]\typora_images/image-20210203224018803.png)

public interface UserDao {
    List<User> getUserList();
}
<?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=绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="dao.UserDao">
    <select id="getUserList" resultType="pojo.User">
        select * from mybatis.user
    </select>
</mapper>

注意点:

1、org.apache.ibatis.binding.BindingException: Type interface dao.UserDao is not known to the MapperRegistry.
这个问题说明核心配置没有注册mapper文件。

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

	……

    <!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册! -->
    <mappers>
        <mapper resource="dao/UserMapper.xml"/>
    </mappers>
</configuration>

执行

 @Test
    public void test01() {

        //获取sqlSession
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:getMapper()
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> userList = mapper.getUserList();
        
        //方式二:
        List<User> userList = sqlSession.selectList("dao.UserDao.getUserList");

        System.out.println(userList);

        //关闭资源
        sqlSession.close();
    }

出错点:

1、java.lang.ClassNotFoundException: Cannot find class: com.mysql.cj.jdbc.Driver
说明:没有安装MySql驱动。(驱动版本跟Mysql版本要匹配)

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.19</version>
</dependency>

2、maven导出问题:找不到对应mapper.xml文件时,有可能是maven没导出该资源的问题。到target文件夹里核对一下。修改pom.xml配置可以解决该问题。

3、注意点

以下都是官方建议的。

SqlSessionFactoryBuilder和SqlSessionFactory

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SfbKxBdV-1628078376566)(D:\笔记\typora_images\image-20210203214901581.png)]

获取SqlSession时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJEmPJd6-1628078376569)(D:\笔记\typora_images\image-20210203214724235.png)]

就是说获取完SqlSession,要在 finally块中写 sqlSession.close(),来确保资源关闭。

三:实现类(mapper.xml)编写

1、增删改查功能

对应标签:<select><insert><delete><update>

属性:

  • id:对应namespace中绑定接口的方法名。
  • resultType:Sql语句执行的返回值的类型。只有一个基本数据类型的参数可以不写。
  • parameterType:传的参数的类型。只有一个基本数据类型的参数可以不写。

注意点:

  • 调用增删改功能后,记得提交事务(sqlSession.commit())

  • 模糊查询时,为防Sql注入:使用通配符:几种方式:

    • Java代码执行时,传递 通配符 % %

      List<User> userList = mapper.getUserLike("%"+name+"%");
      
    • 在Sql拼接中使用通配符 % %

      select * from mybatis.user where name like "%"#{value}"%"
      

2、小技巧

2.1 万能Map

假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!

如:

...
Map<String, Object> map = new HashMap<>();
map.put("userId", 1);//这有一个好处,就是可以灵活put键,不像实体类限制性大。
map.put("username", "Mike");
map.put("adress", "here");

mapper.insertUser(map);

//使用对象可能需要给所有字段都初始化一遍,假如字段或参数过多时很不方便。使用map可以解耦合。

对应的mapper文件:

...
<insert id="insertUser" parameterType="map" >
	insert into mybatis.user(id, pwd) values (#{userId}, #(password));
</insert>
...

四:XML配置解析

注:所有配置最终都会注入到sqlSession实例中。

1、核心配置文件

1.1 环境配置

MyBatis 可以配置成适应多种环境。
**不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**environments 元素定义了如何配置环境。

environments 元素定义了如何配置环境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

注意一些关键点:

  1. 每个 environment 元素定义的环境 ID(比如:id=“development”)。

  2. 默认使用的环境 ID(比如:default=“development”)。

  3. 默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

  4. 事务管理器的配置(比如:type=“JDBC”)。

    1. 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
      1. JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
      2. MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
  5. 数据源的配置(比如:type=“POOLED”)。

    1. dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

      • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

      有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

      1. UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。
      2. POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
      3. JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

1.2 属性(properties)

我们可以通过properties属性来实现引用配置文件。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

例如:

引入外部文件:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/> <!-- 也可以在这里定义属性。注意:同名属性,优先使用外部文件的 -->
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAVGdMF8-1628078376571)(C:\Users\17812\Desktop\笔记(work)]\typora_images\image-20210204153017695.png)

这里说明:properties需要放在最上面。

xml中的标签要注意使用顺序

1.3 类型别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

例如:

<typeAliases>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

1.4 设置

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

官方文档

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

1.5 映射器(mapper)

MapperRegistry:注册绑定我们的Mapper文件。

几种方式:

<!-- 方式一:使用相对于类路径的资源引用 -->(推荐使用)
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 方式二:使用映射器接口实现类的完全限定类名 -->
<!-- 
注意点:
	1.接口和它的Mapper配置文件必须同名!
	2.接口和它的Mapper配置文件必须在同一个包下!
-->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 方式三:将包内的映射器接口实现全部注册为映射器 -->
<!-- 
注意点:
	1.接口和它的Mapper配置文件必须同名!
	2.接口和它的Mapper配置文件必须在同一个包下!
-->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉 MyBatis 去哪里找映射文件。

五:生命周期和作用域

image-20210204162849019

生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,就不再需要它了。
  • 相当于局部变量

SqlSessionFactory

  • 说白了相当于:数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到连接池的一个请求!
  • 每个线程都应该有它自己的 SqlSession 实例。
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用!

六:实用

1、解决属性和数据库字段名不一致的问题

1.1 resultMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9e3JA5s-1628078376573)(C:\Users\17812\Desktop\笔记(work)]\typora_images\image-20210204163916319.png)

当执行:

select * from mybatis.user where id = #{id}
//类型处理器:select id,name,pwd from mybatis.user where id = #{id}

会出现问题:

image-20210204164645475

解决办法:

  1. 起别名

    <select id="..." resultType="pojo.User">
    	select id,name,pwd as password from mybatis.user where id = #{id}
    </select>
    
  2. 使用resultMap

    resultMap:结果集映射

    <!-- 结果集映射 -->
    <resultMap id="UserMap" type="User">
    	<!-- column数据库中的字段,property实体类中的属性 -->
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    </resultMap>
    
    <select id="..." resultMap="UserMap">
    	select * from mybatis.user where id = #{id}
    </select>
    

官方文档

  • resultMap 元素是Mybatis中最重要最强大的元素

  • resultMap 的设计思想是,对于简单的语句根本不需要配置显示的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

  • resultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到它们。

    <!-- 结果集映射 -->
    <resultMap id="UserMap" type="User">
    	<!-- column数据库中的字段,property实体类中的属性 -->
        <!-- 
    	<result column="id" property="id"/>
        <result column="name" property="name"/> 
     	以上两个:字段对应,可以省略。
    	-->
        <result column="pwd" property="password"/>
    </resultMap>
    

resultMap 元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap 元素的概念视图。

结果映射(resultMap)

  • constructor- 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association– 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection– 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator– 使用结果值来决定使用哪个resultMap
    • case– 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

2、日志

日志工厂

如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

Mybatis内置了一些日志工厂:setting里面找。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npmTdpAn-1628078376574)(D:/笔记/笔记(work)]/typora_images/image-20210204173015291.png)

常用:

  • STDOUT_LOGGING

    • 标准日志输出。

    • 在mybatis核心配置文件中,配置日志!

      <settings>
      	<setting name="logImpl" value="STDOUT_LOGGING"/>
      </settings>
      
    • 日志显示:

    image-20210204174131469
  • LOG4J

    • 什么是log4j?

      • 百度百科:

        • Log4j是Apache的一个开源项目,
        • 通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;
        • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
        • 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
    • 配置 log4j

      1. 加入log4j-1.2.8.jar(可以选择log4j的更高版本)到lib下。

        • 如果使用maven项目,也可以选择在pom.xml中新增依赖:
        <!-- 加入log4j支持 -->
        <dependency>
        	<groupId>log4j</groupId>
        	<artifactId>log4j</artifactId>
        	<version>1.2.17</version>
        </dependency>
        
      2. 在CLASSPATH下建立log4j.properties。内容如下:

        log4j.rootCategory=INFO, stdout , R
         
        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n
         
        log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.R.File=D:\\Tomcat 5.5\\logs\\qc.log
        log4j.appender.R.layout=org.apache.log4j.PatternLayout
        log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n
         
        log4j.logger.com.neusoft=DEBUG
        log4j.logger.com.opensymphony.oscache=ERROR
        log4j.logger.net.sf.navigator=ERROR
        log4j.logger.org.apache.commons=ERROR
        log4j.logger.org.apache.struts=WARN
        log4j.logger.org.displaytag=ERROR
        log4j.logger.org.springframework=DEBUG
        log4j.logger.com.ibatis.db=WARN
        log4j.logger.org.apache.velocity=FATAL
         
        log4j.logger.com.canoo.webtest=WARN
         
        log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN
        log4j.logger.org.hibernate=DEBUG
        log4j.logger.org.logicalcobwebs=WARN
        

        详解:(可以根据不同需要自行配置)

        log4j.rootLogger=INFO,consoleAppender,logfile,MAIL
        log4j.addivity.org.apache=true
        #ConsoleAppender,控制台输出
        #FileAppender,文件日志输出
        #SMTPAppender,发邮件输出日志
        #SocketAppender,Socket 日志
        #NTEventLogAppender,Window NT 日志
        #SyslogAppender,
        #JMSAppender,
        #AsyncAppender,
        #NullAppender
        #文件输出:RollingFileAppender
        #log4j.rootLogger = INFO,logfile
        log4j.appender.logfile = org.apache.log4j.RollingFileAppender
        log4j.appender.logfile.Threshold = INFO
        # 输出以上的 INFO 信息
        log4j.appender.logfile.File = INFO_log.html
        #保存 log 文件路径
        Log4j 从入门到详解
        10
        log4j.appender.logfile.Append = true
        # 默认为 true,添加到末尾,false 在每次启动时进行覆盖
        log4j.appender.logfile.MaxFileSize = 1MB
        # 一个 log 文件的大小,超过这个大小就又会生成 1 个日志 # KB ,MB,GB
        log4j.appender.logfile.MaxBackupIndex = 3
        # 最多保存 3 个文件备份
        log4j.appender.logfile.layout = org.apache.log4j.HTMLLayout
        # 输出文件的格式
        log4j.appender.logfile.layout.LocationInfo = true
        #是否显示类名和行数
        log4j.appender.logfile.layout.Title
        =title:\u63d0\u9192\u60a8\uff1a\u7cfb\u7edf\u53d1\u751f\u4e86\u4e25\u91cd\u9519\u8b
        ef
        #html 页面的 < title >
        ############################## SampleLayout ####################################
        # log4j.appender.logfile.layout = org.apache.log4j.SampleLayout
        ############################## PatternLayout ###################################
        # log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
        # log4j.appender.logfile.layout.ConversionPattern =% d % p [ % c] - % m % n % d
        ############################## XMLLayout #######################################
        # log4j.appender.logfile.layout = org.apache.log4j.XMLLayout
        # log4j.appender.logfile.layout.LocationInfo = true #是否显示类名和行数
        ############################## TTCCLayout ######################################
        # log4j.appender.logfile.layout = org.apache.log4j.TTCCLayout
        # log4j.appender.logfile.layout.DateFormat = ISO8601
        #NULL, RELATIVE, ABSOLUTE, DATE or ISO8601.
        # log4j.appender.logfile.layout.TimeZoneID = GMT - 8 : 00
        # log4j.appender.logfile.layout.CategoryPrefixing = false ##默认为 true 打印类别名
        # log4j.appender.logfile.layout.ContextPrinting = false ##默认为 true 打印上下文信息
        # log4j.appender.logfile.layout.ThreadPrinting = false ##默认为 true 打印线程名
        # 打印信息如下:
        #2007 - 09 - 13 14 : 45 : 39 , 765 [http - 8080 - 1 ] ERROR com.poxool.test.test -
        error 成功关闭链接
        ###############################################################################
        #每天文件的输出:DailyRollingFileAppender
        #log4j.rootLogger = INFO,errorlogfile
        log4j.appender.errorlogfile = org.apache.log4j.DailyRollingFileAppender
        log4j.appender.errorlogfile.Threshold = ERROR
        log4j.appender.errorlogfile.File = ../logs/ERROR_log
        log4j.appender.errorlogfile.Append = true
        #默认为 true,添加到末尾,false 在每次启动时进行覆盖
        log4j.appender.errorlogfile.ImmediateFlush = true
        #直接输出,不进行缓存
        # ' . ' yyyy - MM: 每个月更新一个 log 日志
        # ' . ' yyyy - ww: 每个星期更新一个 log 日志
        # ' . ' yyyy - MM - dd: 每天更新一个 log 日志
        # ' . ' yyyy - MM - dd - a: 每天的午夜和正午更新一个 log 日志
        # ' . ' yyyy - MM - dd - HH: 每小时更新一个 log 日志
        # ' . ' yyyy - MM - dd - HH - mm: 每分钟更新一个 log 日志
        Log4j 从入门到详解
        11
        log4j.appender.errorlogfile.DatePattern = ' . ' yyyy - MM - dd ' .log '
        #文件名称的格式
        log4j.appender.errorlogfile.layout = org.apache.log4j.PatternLayout
        log4j.appender.errorlogfile.layout.ConversionPattern =%d %p [ %c] - %m %n %d
        #控制台输出:
        #log4j.rootLogger = INFO,consoleAppender
        log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
        log4j.appender.consoleAppender.Threshold = ERROR
        log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
        log4j.appender.consoleAppender.layout.ConversionPattern =%d %-5p %m %n
        log4j.appender.consoleAppender.ImmediateFlush = true
        # 直接输出,不进行缓存
        log4j.appender.consoleAppender.Target = System.err
        # 默认是 System.out 方式输出
        #发送邮件:SMTPAppender
        #log4j.rootLogger = INFO,MAIL
        log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
        log4j.appender.MAIL.Threshold = INFO
        log4j.appender.MAIL.BufferSize = 10
        log4j.appender.MAIL.From = yourmail@gmail.com
        log4j.appender.MAIL.SMTPHost = smtp.gmail.com
        log4j.appender.MAIL.Subject = Log4J Message
        log4j.appender.MAIL.To = yourmail@gmail.com
        log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
        log4j.appender.MAIL.layout.ConversionPattern =%d - %c -%-4r [%t] %-5p %c %x - %m %n
        #数据库:JDBCAppender
        log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
        log4j.appender.DATABASE.URL = jdbc:oracle:thin:@ 210.51 . 173.94 : 1521 :YDB
        log4j.appender.DATABASE.driver = oracle.jdbc.driver.OracleDriver
        log4j.appender.DATABASE.user = ydbuser
        log4j.appender.DATABASE.password = ydbuser
        log4j.appender.DATABASE.sql = INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n
        ' )
        log4j.appender.DATABASE.layout = org.apache.log4j.PatternLayout
        log4j.appender.DATABASE.layout.ConversionPattern =% d - % c -%- 4r [ % t] %- 5p % c %
        x - % m % n
        #数据库的链接会有问题,可以重写 org.apache.log4j.jdbc.JDBCAppender 的 getConnection() 使用数
        据库链接池去得链接,可以避免 insert 一条就链接一次数据库
        
      3. 核心文件中配置log4j

        <settings>
        	<setting name="logImpl" value="LOG4J"/>
        </settings>
        
    • 简单使用

      1. 在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;

      2. 日志对象获取,参数为当前类的class

        static Logger logger = Logger.getLogger(UserDao.class);
        
      3. 日志级别

        logger.info("...");
        logger.debug("...");
        logger.error("...");
        

3、分页

分页的目的:

  • 减少数据的处理量

3.1 sql语句分页

sql:

语法:
SELECT * from user limit startIndex, pageSize;

3.2 RowBounds分页

例子:

  1. 接口

    List<User> getUserByRowBounds();
    
  2. mapper.xml

    <select id="getUserByRowBounds" resultMap="UserMap">
    	select * from mybatis.user
    </select>
    
  3. 测试

    @Test
    public void getUserByRowBounds() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        
        //RowBounds实现
        RowBounds rowBounds = new RowBounds(1, 2);
        
        //通过java代码层面实现分页
        List<User> userList = sqlSession.selectList("dao.UserMapper.getUserByRowBounds", null, rowBounds);
        
        System.out.println(userList);
        
        sqlSession.close();
    }
    

4、复杂映射处理

4.1 多对一

例子:

sql:表,关联id

image-20210206140831450

实体类:

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
    ...
}

public class Teacher {
    private int id;
    private String name;
}

接口:

public interface StudentMapper {
    //查询所有的学生信息,以及对应的老师的信息!
    public List<Student> getStudent();
}

对应Mapper:

<!--
思路:
	1.查询所有的学生信息
	2.根据查询出来的学生的tid,寻找对应的老师!
-->
<!-- 方式一:按照查询嵌套处理 -->
<select id="getStudent" resultMap="StudentTeacher">
	select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
	<result property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 复杂的属性,我们需要单独处理
		对象:association
		集合:collection
	-->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
	select * from teacher where id = #{id}
</select>
<!-- 方式二:按照结果嵌套处理 -->
<select id="getStudent" resultMap="StudentTeacher">
	select s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id
</select>
<resultMap id="StudentTeacher" type="Student">
	<result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
    	<result property="name" column="tname"/>
    </association>   
</resultMap>

4.2 一对多

例子:sql表同上

实体类:

public class Teacher {
    private int id;
    private String name;
    
    //一个老师拥有多个学生
    private List<Student> students;
    
    ...
}

public class Student {
    private int id;
    private String name;
    
    private int tid;
}

接口:

public interface TeacherMapper {
    
    //获取指定老师,以及老师下的所有学生信息
    Teacher getTeacher(@Param("tid") int id);
}

mapper文件:

<!-- 按结果嵌套查询 -->
<select id="getTeacher" resultMap="TeacherStudent">
	select s.id sid, s.name sname, t.name tname, t.id tid
    from student s, teacher t
    where s.tid = t.id and t.id = #{tid}
</select>

<resultMap id="TeacherStudent" type="Teacher">
	<result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!-- 集合中的泛型信息,我们使用ofType获取 -->
    <collection property="students" ofType="Student">
    	<result property="id" column="sid"/>
        <result property="name" column="tname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>
<!-- 按查询嵌套处理 -->
<select id="getTeacher" resultMap="eacherStudent">
	select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
	<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id = "getStudentByTeacherId" resultType="Student">
	select * from mybatis.student where tid = #{tid}
</select>

5、动态SQL

动态SQL就是指根据不同的条件生成不同的SQL语句。

Mybatis提供一些功能,帮助我们可以在SQL层面,去执行一个逻辑代码。

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了。

官方文档

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

5.1 SQL片段

有时候,我们可能会将一些功能的部分抽取出来,方便复用!

  1. 使用SQL标签抽取公共的部分

    <sql id="if-title-author">
    	<if test="title != null">
        	title = #{title}
        </if>
        <if test="author != null">]
        	and author = #{author}
        </if>
    </sql>
    
  2. 在需要使用的地方使用include标签引用即可

    <select id="queryBlogIF" parameterType="map" resultType="blog">
    	select * from mybatis.blog
        <where>
        	<include refid="if-title-author"></include>
        </where>
    </select>
    

使用建议:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

七:使用注解开发

1、面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。
  • 根本原因:解耦合,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范、约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
    • 一个个体可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别:

  • 面向对象是指:我们考虑问题时,以对象为单位,考虑它的属性及方法。
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构。

2、使用注解开发

官方文档说明:

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

  • 底层:反射实现

  • 本质:动态代理

例子:

  1. 注解在接口上实现

    @Select("select * from user")
    List<User> getUsers();
    
  2. 需要在核心配置文件中绑定接口!

    <mappers>
    	<mapper class="dao.UserMapper"/>
    </mappers>
    
  3. 测试

3、Mybatis详细执行流程

Mybatis详细执行流程

4、使用注解进行CRUD

例:

public interface UserMapper {
    
    @Select("select * from user")
    List<User> getUsers();
    
    //方法存在多个参数,所有的参数前面必须加上 @Param("..")注解
    @Select("select * from user where id = #{id}")
    User getUserByID(@Param("id") int id);
    
    @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
    int addUser(User user);
    
    @Update("update user set name=#{name},pwd=#{password}")
    int updateUser(User user);
    
    @Delete("delete from user where id = #{id}")
    int deleteUser(@Param("id") int id);
}

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略。
  • SQL中引用的就是@Param()中设定的属性名!

注:

1、创建sqlSession实例时,可以设置自动提交事务:

sqlSessionFactory.openSession(true); //openSession有很多重载的构造器

2、要记得将接口绑定到核心配置文件中。

八:缓存

1、简介

1、什么是缓存(Cache)?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2、为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

3、什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。

2、Mybatis缓存

官方文档

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

3、一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,不必须再去查询数据库。

缓存失效的情况:

  1. 查询不同的东西
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存。
  3. 查询不同的Mapper.xml
  4. 手动清理缓存

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段。

4、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制:
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

使用步骤:

  1. 开启全局缓存

    <!-- 显式地开启全局缓存(默认是开启的) -->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在要使用二级缓存的Mapper中开启

    <!-- 在当前Mapper.xml中使用二级缓存 -->
    <cache />
    

    也可以自定义参数

    <cache eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
    

平时用的标签,也有设置使用/不使用缓存的功能:

image-20210206203409407

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效。
  • 所以的数据都会先放在一级缓存中
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

5、缓存原理

缓存顺序:

  1. 先看二级缓存中有没有
  2. 再看一级缓存中有没有
  3. 都没有再查询数据库

6、自定义缓存

EnCache

九:其他

1、#{} 和 ${} 的区别

  • 前言

    1. $符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。
    2. 预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
  • 区别:举例说明:

    • ${param}传递的参数会被当成sql语句中的一部分,比如传递表名,字段名
       例子:(传入值为id)
       	order by ${param} 
       则解析成的sql为:
       	order by id
       
      #{parm}传入的数据都当成一个字符串,会对自动传入的数据加一个双引号""
       例子:(传入值为id)
       	select * from table where name = #{param}
       则解析成的sql为:
       	select * from table where name = "id"
       
      为了安全,能用#的地方就用#方式传参,这样可以有效的防止sql注入攻击
      
    • sql注入举例

      某个网站的登录验证的SQL查询代码为:
      	strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
      恶意填入
      userName = "1' OR '1'='1";
      与passWord = "1' OR '1'='1";
       此时,将导致原本的SQL字符串被填为
      strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
       也就是实际上运行的SQL命令会变成下面这样的
      strSQL = "SELECT * FROM users;"
      这样在后台帐号验证的时候巧妙地绕过了检验,达到无账号密码,亦可登录网站。所以SQL注入攻击被俗称为黑客的填空游戏。
      

进一步说明:

动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。

  • 在下面的语句中,如果 username 的值为 zhangsan,则两种方式无任何区别:

    • select * from user where name = #{name};
    • select * from user where name = ${name};
  • 其解析之后的结果均为

    • select * from user where name = ‘zhangsan’;
  • 但是 #{} 和 ${} 在预编译中的处理是不一样的。

    • #{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:

      • select * from user where name = ?;
    • 而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成

      • select * from user where name = ‘zhangsan’;

以上,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。

那么,在使用过程中我们应该使用哪种方式呢?

答案是,优先使用 #{}。因为 ${} 会导致 sql 注入的问题。看下面的例子:

  • select * from ${tableName} where name = #{name}
    在这个例子中,如果表名为 user; delete user; –
  • 则动态解析之后 sql 如下:
    • select * from user; delete user; – where name = ?;
    • –之后的语句被注释掉,而原本查询用户的语句变成了查询所有用户信息+删除用户表的语句,会对数据库造成重大损伤,极大可能导致服务器宕机。

但是表名用参数传递进来的时候,只能使用 ${} ,具体原因可以自己做个猜测,去验证。这也提醒我们在这种用法中要小心sql注入的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值