mybatis基础教程

本文详细介绍了MyBatis的配置文件,包括环境配置、类型别名、设置、映射器等,以及如何处理SQL查询、结果映射、动态SQL和缓存。此外,还探讨了一对一、一对多的关联映射和日志配置。通过对MyBatis配置的学习,可以更好地理解和使用MyBatis进行数据库操作。
摘要由CSDN通过智能技术生成

mybatis

xml配置文件

  • mybatis-config.xml配置代码:(先导jar包)

    <?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>
      
      
      <!-- 定义很多环境,默认环境是development -->
        <environments default="development">
    		<!-- 定义一个名字叫development的环境 -->
    		<environment id="development">
    			<!-- 事务管理采用的方式是JDBC -->
    			<transactionManager type="JDBC" />
    			<!-- 数据源采用POOLED连接池方式 -->
    			<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>
      
      
      <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
      </mappers>
    </configuration>
    
  • 在元素中,,,有先后顺序。

  • 属性(properties)

    • 元素的resource属性,说明了属性文件的所在位置,一般是在类路径中。
    • 创建一个db.properties文件
    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/mybatisdb?useUnicode=true&characterEncoding=UTF-8
    username=root
    password=admin
    
    • 在里最上方添加配置(引入文件或者在标签内写值两种方法)

      如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
      1.首先读取在 properties 元素体内指定的属性。
      2.然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取		属性文件,并覆盖之前读取过的同名属性。
      3.最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
      4.因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
      
      <!-- 引入数据库文件 -->
      <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
      </properties>
      
  • 设置(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>
      
  • 类型别名(typeAliases)

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

      <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
        <typeAlias alias="Comment" type="domain.blog.Comment"/>
        <typeAlias alias="Post" type="domain.blog.Post"/>
        <typeAlias alias="Section" type="domain.blog.Section"/>
        <typeAlias alias="Tag" type="domain.blog.Tag"/>
      </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 {
          ...
      }
      
    • 下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

    在这里插入图片描述

  • 环境配置(environments)

    • MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

    • 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

    • 所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例。

      <!-- 定义很多环境,默认环境是development -->
          <environments default="development">
      		<!-- 定义一个名字叫development的环境 -->
      		<environment id="development">
      			<!-- 事务管理采用的方式是JDBC -->
      			<transactionManager type="JDBC" />
      			<!-- 数据源采用POOLED连接池方式 -->
      			<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>
      		
      		<!-- <environment id="test"> 测试环境 要使用只需要修改environments的default属性值
      			事务管理采用的方式是JDBC
      			<transactionManager type="JDBC" />
      			数据源采用POOLED连接池方式
      			<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>
      
    • 事务管理器(transactionManager)

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

    • 数据源(dataSource)

    • 有三种内建的数据源类型(也就是 type=“[UNPOOLED|POOLED|JNDI]”):
      - UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
      - POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
      - JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

  • 映射器(mappers)

    • 我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

    方法一:

    <!-- 使用相对于类路径的资源引用 -->
    <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>
    

    方法三:

    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    

    方法四:

    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    

xml映射文件(Mapper)

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

cache – 该命名空间的缓存配置。
cache-ref – 引用其它命名空间的缓存配置。
resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
sql – 可被其它语句引用的可重用语句块。
insert – 映射插入语句。
update – 映射更新语句。
delete – 映射删除语句。
select – 映射查询语句。
  • 基础配置代码

    • 映射文件中,根元素是元素,它有一个namespace属性,用来管理SQL映射语句的名称.
    • 在同一个名字空间中,映射语句是不能重名的。namespace属性的取值需要是接口的全限定名
    <?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.tcc.dao.RoomieMapper">
    
        
    </mapper>
    

    为了区分SQL语句的语义,MyBatis提供了、、、四类SQL操作,分别执行查询、插入、删除和更新数据操作。

  • CRUD(增删改查)环境配置

    • 先在mysql数据库里创建"roomie"表并添加属性和值

      create table roomie(
      	id int auto_increment not null PRIMARY key COMMENT '宿舍成员id',
      	name varchar(50) not null comment '宿舍成员姓名',
      	age int COMMENT '宿舍成员年龄',
      	suSheHao int COMMIT '宿舍号',
      	bedNumber int COMMIT '床位号'
      )ENGINE=INNODB default CHARSET=utf8;
      
    • 创建javaWeb项目

    • 导入mysql包并在src文件里面创建"mybatis-config.xml"配置文件修改映射器mappers里的路径

    ```
    • 创建实体类包并生成getter/setter、toString、有参/无参构造方法

      package com.tcc.entity;
      
      import org.apache.ibatis.type.Alias;
      
      @Alias("roomie")
      public class Roomie {
      	//属性
      	private int id;
      	private String name;
      	private int age;
      	private int suSheHao;
      	private int bedNumber;
      
    • 创建dao包并添加RoomieMapper接口和RoomieMapper.xml配置文件

      package com.tcc.dao;
      public interface RoomieMapper {
      
      	
      }
      
      <?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.tcc.dao.RoomieMapper">
          
      </mapper>
      
    • 创建测试包添加测试类

      package com.tcc.test;
      
      import org.apache.ibatis.session.SqlSession;
      import com.tcc.dao.RoomieMapper;
      import com.tcc.entity.Roomie;
      import com.tcc.util.MyBatisUtil;
      
      public class RoomieTest {
      
      	public static void main(String[] args) {
      		String configFile = "mybatis-config.xml";  
          // 输入流  
          InputStream in = null;  
          // sql会话工厂  
          SqlSessionFactory sf = null;  
          // sql会话 <=====> connection  
          SqlSession session = null; 
          try {  
            // sql会话工厂创建者  
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();  
            // 可以读取configFile配置文件  
            in = Resources.getResourceAsStream(configFile);  
            // 得到sql会话工厂  
            sf = builder.build(in);  
            // 得到sql会话  
            session = sf.openSession();  
            // 得到dao对象  
            RoomieMapper rdao = session.getMapper(RoomieMapper.class);  
             
            session.close();  
          } catch (IOException e) {
            e.printStackTrace();  
          }
      	
      	}
      
      }
      
      
  • select查询

    • 在dao层接口添加查询方法

      无参查询

      /**
      	 * 查询宿舍所有人
      	 * @return
      	 */
      List<Roomie> showAll();
      

      有参查询

      • 多种不同基本类型的参数需要在参数里面添加【@Param(“…”)】注释,为了规范单一参数也添加。特殊类型的参数不需要加
      /**
      	 * 按照年龄进行查询
      	 */
      

    List showAge(@Param(“age”)int age);

    - 在Mapper映射文件中写查询标签和语句
    
    - **id:**为这个查询的名字**,在同一个namespace中,id取值不能相同**。
    - **resultType:**为得到的结果返回值的类型查询一般为类型的全限定名
    
    无参查询。
    
    ```xml
    <!-- 查询宿舍所有人 -->
    <select id="showAll" resultType="com.tcc.entity.Roomie">
      select * from roomie;
    </select>
    

    有参查询

    • **parameterType:**为参数的类型,基本类型的参数可以省略。
    • sql语句中需要用参数的地方要使用占位符#{…}或者${…}。通常用#{},防止sql注入。
    <!-- 按照年龄进行查询 -->
    <select id="showAge" parameterType="int" resultType="roomie">
      select * from roomie where age = #{age};
    </select>
    
    • 测试:

      无参查询

      List<Roomie> rom = roomieDao.showAll();
      for (Roomie roomie : rom) {
        System.out.println(roomie);
      

    }

    
    有参查询
    
    ```java
    List<Roomie> rom = roomieDao.showAge(19);
    		for (Roomie roomie : rom) {
    			System.out.println(roomie);
    	}
    
  • 模糊查询

    • 在dao接口写方法

      /**
      	 * 根据名字模糊查询
      	 */
      List<Roomie> showName(@Param("name")String name);
      

    • 在Mapper中写inset标签和语句

      <!-- 根据名字模糊查询 -->
      <select id="showName" resultType="roomie" >
        <!-- select * from roomie where name like #{name}   最好不要这样-->
        <!-- select * from roomie where name like "%"#{name}"%" -->
        <!-- select * from roomie where name like '%${name}%'  -->
        <!-- 最优秀的写法 -->
        select * from roomie where name like CONCAT('%',#{name},'%');
      </select>
      
    • 测试

      List<Roomie> rom = roomieDao.showName("李");
      for (Roomie roomie : rom) {
        System.out.println(roomie);
      }
      
  • inset添加

    • 在dao层写方法

      /**
      	 * 增加宿舍成员
      	 */
      int add(Roomie roomie);
      
    • 在Mapper中写配置

      <insert id="add" parameterType="Roomie">
        insert into roomie(name,age,suSheHao,bedNumber) values(#{name},#{age},
        #{suSheHao},#{bedNumber});
      </insert>
      
    • 测试

      Roomie roomie = new Roomie(0, "小黑",25,2509,9);
      int num = roomieDao.add(roomie);
      //增删改必须提交事务
      session.commit();
      
  • update修改

    • 在接口写方法

      /**
      	 * 修改宿舍成员
      	 */
      int update(Roomie roomie);
      
    • 在mapper写配置

      <update id="update" parameterType="Roomie">
        update roomie set name=#{name},age=#{age},suSheHao=#{suSheHao},
        bedNumber=#{bedNumber} where id=#{id};
      </update>
      
    • 测试

      Roomie roomie = new Roomie(14,"小白",30,2508,10);
      int num = roomieDao.update(roomie);
      //提交事务
      session.commit();
      
  • delete删除

    • 在接口写方法

      /**
      	 * 删除宿舍成员
      	 */
      int delete(@Param("id")int id);
      
    • 在mapper写配置

      <delete id="delete" parameterType="int">
        delete from roomie where id=#{id};
      </delete>
      
    • 测试

      int num = roomieDao.delete(16);
      		if(num>0){
      			System.out.println("删除成功")
      		}
      		session.commit();
      
  • CRUD总结

    1. 增删改查的id值都不许重复。
    2. 只有在查询标签里才需要写【resultType】属性。
    3. 在增删改的测试类最后必须提交事务【session.commit()】!
    4. 基本参数在dao层都要用【@Param(“…”)】注释,特殊类型的参数不需要加。
    5. mapper配置里面的参数占位符都要用【#{…}】
  • Map参数(拓展)

    • 在添加和修改的时候我们需要传很多参数,如果参数是对象的话我们都需要给每个属性赋值会很麻烦。

    • 在添加的时候我们只想给这条记录添加一些属性值而不是全部就可以用到map参数。很灵活。

      • 在dao层添加方法

        /**
        	 * 修改宿舍成员用Map参数
        	 */
        int updateForMap(Map map);
        
      • 在mapper中写配置(参数类型要改为map)

        <!-- 修改宿舍成员用Map参数 -->
        <update id="updateForMap" parameterType="map">
          update roomie set name=#{name} where id=#{id};
        </update>
        
      • 测试

        //修改宿舍成员用Map参数
        Map map = new HashMap<String,Object>();
        		map.put("id", 16);
        		map.put("name", "小红");
        		int num = roomieDao.updateForMap(map);
        		if(num>0){
        			System.out.println("修改成功");
        		}
        		session.commit();
        
  • 定义别名

    • 在MyBatis中,别名有两种:一种是系统定义的别名,另一种是自定义的别名。别名的最大好处是:可以简化操作。

    • 系统定义的别名

      在这里插入图片描述

    • 自己定义别名

      方法一

      <!--省略dtd声明部分 --> 
      <configuration>  
      	<!-- 配置别名 --> 
        <typeAliases>  
          <typeAlias alias="news" type="com.ysdit.news.entity.News"/>  
        </typeAliases>  
        <!-- 省略配置数据源和事务管理器 ,省略映射文件信息--> 
      </configuration>
      

      方法二

      • 如果需要配置的别名比较多,那么还可以使用元素一次性的把指定包中所有的类型,按照类名首字母小写的规则配置别名。

        <!--省略dtd声明部分 --> 
        <configuration>  
        	<!-- 配置别名 --> 
          <typeAliases>  
            <package name="com.ysdit.news.entity"/> 
          </typeAliases>  
          <!-- 省略配置数据源和事务管理器 ,省略映射文件信息--> 
        </configuration>
        
      • 通过上面代码的配置,那么com.ysdit.news.entity包中所有的实体类都被配置的有别名。配置别名之后,在接口映射文件中,就可以使用别名进行简化,如下面代码所示:

        <select id="selectTopNews" resultType="news" parameterType="date">  
          SELECT * FROM t_biz_news WHERE UPPER(is_top) = 'Y' AND 
          pdate=#{date,jdbcType=DATE}; 
        </select> 
        

      • 如果使用配置了整个包类型的别名,但还需要为某个类单独制定别名,可以在类中使用@Alias注解进行配置,如下代码所示。

        @Alias("user") 
        public class SysUser {  
          private String ucode; //用户编号  
          private String ulogin; //登录名 
          private String upass; //登录密码  
          private String uemail; //电子邮件 
          
          /*省略getter和setter方法及构造方法*/ 
        } 
        
  • 结果集映射

    • 在查询语句中,select子句使用了通配符“*”,也就是查询所有的字段数据。

    • 数据查询出来之后,MyBatis自动把每个记录的数据封装成一个新闻实体对象,那么MyBatis如何知道某列的数据赋值给实体的哪一个属性呢?

    • 默认情况下,MyBatis采取的是通过结果集中列名与属性名同名赋值的方法。

    • 实体类属性名称保持和数据库表列名同名。要是实体属性名称与表列名不一致,该如何处理?

    • 方法一

      • 第一种办法是查询时,通过给列名取别名的方式,使得别名与实体属性名保持一致。例如新闻实体中新闻标题属性名称为newsTitle,但是news表中新闻标题列名为title,那么查询语句select自己需要对title列取别名为newsTitle。

      • 这种处理办法不足之处是如果select子句列比较多的情况下,会降低查询语句的阅读性。

        <select id="selectMaximallySharedNews" resultType="com.ysdit.news.entity.News">
          select title as newsTitle <!--省略其他列-->  
          from t_biz_news 
          where scnt = (select max(scnt) from t_biz_news) 
        </select> 
        
    • 方法二(常用)

      • 第二种办法是采用结果映射(resultMap),在结果映射中,定义好属性与列的对应关系,如此就省去了在select子句中定义别名,不仅查询语句变得简单,而且resultMap还可以被重用。

        <?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.tcc.dao.RoomieMapper">
        
          <!-- 结果集映射-->
          <resultMap type="Roomie" id="RoomieMap">
            <!-- 需要改哪个就写那个 -->
            <id column="id" property="id" jdbcType="INTEGER"/>
            <result column="name" property="name" jdbcType="VARCHAR"/>
            <result column="age" property="age" jdbcType="INTEGER"/>
            <result column="suSheHao" property="suSheHao" jdbcType="INTEGER"/>
            <result column="bedNumber" property="bedNumber" jdbcType="INTEGER"/>
          </resultMap>
        
            
        </mapper>
        
  • MyBatis配置日志信息

    • 如果已经设置Mybatis记录日志使用log4j,则日志中会记录所执行的sql语句及参数,所以把log4j日志信息输出控制台,即可观看sql语句和参数。首先,必须在项目中添加log4j-1.2.17.jar库文件

    • 定义一个日志文件log4j.properties,log4j日志信息输出到控制台的属性配置如下所示:

      log4j.rootLogger=debug, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n 
      
    • 在mybatis-config.xml中,配置日志文件,具体配置代码如下:

      <!--省略dtd声明部分 --> 
      <configuration>  
        <!-- 配置日志系统,采用log4j作为日志工具(前提是已经加入log4j日志工具,即需要有log4j的jar包和属性文件) -->  
        <settings>  
          <setting name="logImpl" value="LOG4J"/>  
        </settings>  
        <!-- 配置别名 -->   
        <!-- 省略配置数据源和事务管理器 ,省略映射文件信息--> 
      </configuration> 
      
  • 多对一(association)

    • 不用association元素实现关联
      • 不使用association元素实现对象之间的关联关系,是最简单的一种方式,也是最能够反映resultMap功能作用的一种方式。

        • 创建teacher和student表并创建外键

          create table teacher(
          	id int auto_increment not null PRIMARY key COMMENT '教师id',
          	name varchar(50) not null comment '教师姓名'
          )ENGINE=INNODB default CHARSET=utf8;
          
          create table student(
          	id int auto_increment not null PRIMARY key COMMENT '学生id',
          	name varchar(50) not null comment '学生姓名',
          	f_tid int not null,
          FOREIGN key(f_tid) REFERENCES teacher(id)
          )ENGINE=INNODB default CHARSET=utf8;
          
        • 分别创建实体类、接口、mapper.xml

        • 创建util类导入MyBatisUtil.java工具类

          package com.tcc.util;
          
          import java.io.IOException;
          
          import org.apache.ibatis.io.Resources;
          import org.apache.ibatis.session.SqlSession;
          import org.apache.ibatis.session.SqlSessionFactory;
          import org.apache.ibatis.session.SqlSessionFactoryBuilder;
          
          public class MyBatisUtil {
          	private static String CONFIG_FILE = "mybatis-config.xml";
          	private static SqlSessionFactory sessionFactory;
          	private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
              /**
          	* 静�?�初始化块,完成创建唯一的SqlSessionFactory对象
          	*/
          	static {
          		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
          		try {
          			sessionFactory = builder.build(Resources
          					.getResourceAsStream(CONFIG_FILE));
          } catch (IOException e) {
          			e.printStackTrace();
          		}
          	}
              /**
          	* 线程安全地创建SqlSession对象
          	* @return SqlSession对象
          	*/
          	public static SqlSession getSqlSession() {
          		SqlSession session = threadLocal.get();
          		if (session == null) {
          			session = sessionFactory.openSession();
          			threadLocal.set(session);
          		}
          		return session;
          	}
          	public static void closeSqlSession() {
          		SqlSession session = threadLocal.get();
          		if (session != null) {
          			session.close();
          			threadLocal.set(null);
          		}
          	}
          	/**
          	* 获得Dao接口实现�?
          	* @param clazz Dao接口类型
          	* @return Dao接口实现�?
          	*/
          	public static <T> T getMapper(Class<T> clazz){
          		return getSqlSession().getMapper(clazz);
          	}
          }
          
          
          
        • 在StudentMapper接口层写接口

          package com.tcc.dao;
          
          import java.util.List;
          
          import com.tcc.entity.Student;
          
          public interface StudentMapper {
          	/**
          	 * 查询所有学生
          	 */
          	public List<Student> showAll();
          	
          	public List<Student> showAll2();
          	
          	public List<Student> showAll3();
          	
          	public List<Student> showAll4();
          	
          
          }
          
      • 在mapper中写配置

        • :代码告诉MyBatis,查询结果数据集中,catname列的数据应该绑定到文档对象中teacher对象的 name属性上。
        <select id="showAll3" resultMap="StudentMap3">
          select s.id sid, s.name sname, t.name tname 
          from student s INNER JOIN teacher t
        </select>
        
        <resultMap type="student" id="StudentMap3">
          <id property="id" column="sid"/>
          <result property="name" column="sname"/>
          <result property="teacher.name" column="tname"/>
        </resultMap>
        
      • 测试

        public static void main(String[] args) {
          // TODO Auto-generated method stub
          SqlSession session = MyBatisUtil.getSqlSession();
          StudentMapper st = session.getMapper(StudentMapper.class);
          Logger logger = Logger.getLogger(Test.class);
        
          List<Student> slist = st.showAll4();
          for (Student student : slist) {
            System.out.println(student);
          }
        
          session.close();
        
        }
        
      • 结果

        DEBUG [main] - <==      Total: 4
        Student [id=1, name=田臣城, teacher=Teacher [id=1, name=冯杰世]]
        Student [id=2, name=刘金召, teacher=Teacher [id=1, name=冯杰世]]
        Student [id=4, name=董闫笑, teacher=Teacher [id=1, name=冯杰世]]
        Student [id=5, name=王钰龙, teacher=Teacher [id=1, name=冯杰世]]
        
    • 使用association实现对象关联
      • 方法一:(相当于子查询)

        <select id="showAll" resultMap="studentMap1">
          select * from student
        </select>
        
        <resultMap type="student" id="studentMap1">
          <id property="id" column="id"/>
          <result property="name" column="name"/>
          <association property="teacher" column="f_tid" javaType="teacher" select="showTeacher"/>
        </resultMap>
        
        <select id="showTeacher" resultType="teacher">
          select * from teacher where id = #{f_tid}
        </select>
        
      • 方法二:

        <select id="showAll2" resultMap="studentMap2">
          select s.id as sid,s.name as sname,t.name as tname 
          from student s,teacher t 
          where s.f_tid=t.id
        </select>
        
        <resultMap type="student" id="studentMap2">
          <id property="id" column="sid"/>
          <result property="name" column="sname"/>
          <association property="teacher" javaType="teacher">
            <result property="name" column="tname"/>
          </association>
        </resultMap>
        

      • 方法三:()

        <resultMap type="teacher" id="teacherMap4">
          <result property="id" column="tid"/>
          <result property="name" column="tname"/>
        </resultMap>
        
        <select id="showAll4" resultMap="studentMap4">
          select s.id sid, s.name sname, t.name tname, t.id tid from student s INNER JOIN teacher t
        </select>
        
        <resultMap type="student" id="studentMap4">
          <result property="id" column="sid"/>
          <result property="name" column="sname"/>
          <association property="teacher" resultMap="teacherMap4"/>
        </resultMap>
        
  • 一对多(collection)

    • 在TeacherMapper中写接口

      public interface TeacherMapper {
      	
      	/**
      	 * 根据id查询当前老师对应的所有学生
      	 * @return
      	 */
      	List<Teacher> showAll(@Param("f_tid")int f_tid);
      	
      	List<Teacher> showAll2(@Param("f_tid")int f_tid);
      	
      	
      }
      
    • 在Mapper.xml中写配置

      • 方法一

        <select id="showAll" resultMap="teacherAndStudent">
          select s.id as sid,s.name as sname,t.id as tid,t.name as tname,s.f_tid 
          from student s, teacher t 
          where t.id=#{f_tid};
        </select>
        
        <resultMap type="teacher" id="teacherAndStudent">
          <result property="id" column="tid"/>
          <result property="name" column="tname"/>
          <collection property="students" ofType="student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="f_tid" column="f_tid"/>
          </collection>
        
        </resultMap>
        
      • 方法二

        <select id="showAll2" resultMap="teacherAndStudent2">
          select * from teacher where id = #{f_tid};
        </select>
        
        <resultMap type="teacher" id="teacherAndStudent2">
          <result property="id" column="id"/>
          <collection property="students" javaType="ArrayList" ofType="student" select="showStudent" column="id" />
        </resultMap>
        
        <select id="showStudent" resultType="student">
          select * from student where f_tid=#{f_tid}
        </select>
        
  • 动态SQL

    • MyBatis最强大的特性之一是它的动态语句功能。

    • 如果你以前使用JDBC 或者类似的框架,你就会明白把SQL语句条件连接在一起是多么的痛苦,一点都不能疏忽空格和逗号等。动态语句完全能解决这些烦恼。

    • 动态SQL元素对于任何使用过JSTL或者类似于XML之类的文本处理的人来说,都是非常熟悉的。

    • MyBatis 使用了基于强大的OGNL(Object-Graph Navigation Language 的缩写,它是一种功能强大的表达式语言)表达式来避免了大部分其它的元素。

    • if标签
      • 元素的作用在于,当test满足其中条件表达式时,标签体中的内容会追加到SQL语句当中。

      • 如果所有条件都不成立,为了保证where子句的语法正确,需要在所有之前添加“1 = 1”这么一个能够是“真”的表达式。

      • if 标签执行的原理和JSTL标签中的 if 标签原理几乎完全一样。

      • 哪个条件满足就往sql语句后面添加哪个条件相当于JAVA的if语句

        • 添加接口

          /**
          	 * 根据员工姓名或点赞量查询员工
          	 */
          List<Staff> showNameAndDianZan(Map map);
          
        • 添加配置

          <select id="showNameAndDianZan" parameterType="map" resultType="staff">
            select * from staff where 1=1 
            <if test="name != null and name != ''">
              name=#{name},
            </if>
            
            <if test="dianZan != null">
              dianZan=#{dianZan}
            </if>
          </select>
          
        • 测试

          public static void main(String[] args) {
          		// TODO Auto-generated method stub
          		SqlSession session = MyBatisUtil.getSqlSession();
          		StaffMapper sDao = session.getMapper(StaffMapper.class);
          		
          		Map map = new HashMap();
          		map.put("name", "张");
          		map.put("dianZan", 9999);
          		//map.put("id", 1);
          		
          		List<Staff> stList = sDao.showNameAndDianZan(map);
          		for (Staff staff : stList) {
          			System.out.println(staff);
          		}
            
          		session.close();
          	}
          
    • trim、where和set替换
      • trim
        <trim>是一个自定义标签,元素的作用是对SQL片段做前后缀处理。 
        prefix:前缀替换或新增内容,即替换后的前缀 
        prefixOverrides:前缀匹配条件,即要被替换掉的前缀 
        suffix:后缀替换或新增内容,即替换后的后缀 
        suffixOverrides:后缀匹配条件,即要被替换掉的后缀   
        执行原理:当trim标签内容SQL不为空时,对SQL的前后缀处理是,首先会删除前后缀匹配条件匹配到的内容,然后增加替换后的前后缀内容。 
        
        • 比如下面代码中trim的写法全等于set标签。可以简单的理解为set标签内容不为空,则给内容前加set 内容后去逗号(,)。

          <trim suffix="" suffixOverrides="," prefix="set" prefixOverrides="">  
            <if test="age != null">
              age=#{age}  
            </if>  
            <if test="name != null">  
             and name =#{name}  
            </if>  
          </trim> 
          
        • 比如下面代码中trim的写法全等于where标签。可以简单的理解为where标签内容不为空,则先去掉内容前的and,or等,再在内容前加where。

          <trim prefix="where" prefixOverrides="AND | OR">
            <if test="id != null">  
              and id=#{id}  
            </if>  
          </trim> 
          
      • where
        • 元素只有在其内部标签有返回内容时才会在动态语句上插入WHERE 条件语句。并且,如果WHERE子句以AND或者OR打头,则打头的AND或OR 将会被移除

        • 配置

          <select id="showNameAndDianZan" parameterType="map" resultType="staff">
            select * from staff  
            <where>
            	<if test="name != null and name != ''">
            	  name=#{name}
           		</if>
            
           		<if test="dianZan != null">
             	and	dianZan=#{dianZan}
            	</if>
            </where>
          </select>
          

      • set
        • 元素将动态的配置SET关键字,也用来剔除追加到条件末尾的任何不相关的逗号。比如下面代码中trim的写法全等于set标签。

        • 接口

          /**
          	 * 修改员工信息
          	 */
          int update(Map map);
          
        • 配置

          <update id="update" parameterType="map">
            update staff
            <!-- set标签动态添加set 会把最后一个条件后面无用的“,”自动删除 -->
            <set>
              <if test="name != null and name != ''">
          			 name=#{name},
          		</if>
          		<if test="dianZan != null">
          			dianZan=#{dianZan}
          		</if>
            </set>
            where id=#{id}
          </update>
          
    • choose分支结构
      • 有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个,类似于Java的switch语句。这时候我们应该怎么办? 其实,在MyBatis中给我们提供了choose元素。

      • MyBatis 计算结构中的测试条件的值,且使用第一个值为 true 的子句。如果没有条件为 true的子句, 则使用内的子句。

      • 同样的可以发现,choose,when,otherwise组合标签的用法与JSTL中的几乎一样。

      • 接口

        /**
        	 * 根据员工姓名或点赞量查询员工
        	 */
        List<Staff> showNameAndDianZan(Map map);
        
      • 配置

        <select id="showNameAndDianZan" parameterType="map" resultType="staff">
          select * from staff
          <!-- where标签,如果第一个条件前面有and则自动删除and,如果没有条件则不添加where -->
          <where>
            <!-- choose相当于switch语句,如果第一条满足则只走第一条,如果都不满足则走otherwise -->
            <choose>
              <when test="name != null">
                and name like "%"#{name}"%"
              </when>
              <when test="dianZan != null">
                and dianZan=#{dianZan}
              </when>
        
              <otherwise>
                and id = #{id}
              </otherwise>
            </choose>
          </where>
        </select>
        
      • 测试

        public static void main(String[] args) {
        		// TODO Auto-generated method stub
        		SqlSession session = MyBatisUtil.getSqlSession();
        		StaffMapper sDao = session.getMapper(StaffMapper.class);
        		
        		Map map = new HashMap();
          //哪个条件先满足就走哪个,都不满足就走otherwise里的条件。最多最少都只会走一个条件
        		map.put("name", "张");
        		map.put("dianZan", 9999);
        		map.put("id", 1);
        		
        		List<Staff> stList = sDao.showNameAndDianZan(map);
        		for (Staff staff : stList) {
        			System.out.println(staff);
        		}
          
        		session.close();
        	}
        
    • foreach循环
      • 我想给用户表中的所有员工点赞量+1,应该怎么做? 很显然,在多选的情况下,一般都需要使用IN子查询。但是今天我们学习的MyBatis中也提供了foreach供开发人员使用。

      • 标签作用是循环产生SQL片段,插入到已有SQL语句中。collection:需要遍历的集合, item:遍历出的每一项,index:迭代状态,open:开始,separator:分割符,close:结束。

        其生成内容时,需要从集合参数获取,集合由collection属性指定,而如果传入参数是java.util.List 这里固定写法 collection="list" 或者 collection="collection"。内容可以被前后包围,其开始符号由open属性指定,结束符号用close属性指定,多个单值参数内容的分隔符由separator指定。如果需要产生IN子查询,那么以“(”符号开始,“)”符号结束,内容用“,”号分隔。
        
      • 接口

        /**
        	 * 根据id给员工的点赞量+1
        	 */
        int updateById(List<Integer> ids);
        
      • 配置

        <update id="updateById" parameterType="Integer">
          update staff set dianZan=dianZan+1 where id in
        
          <!-- <foreach>标签作用是循环产生SQL片段,插入到已有SQL语句中。
           collection:需要遍历的集合, item:集合名
           index:迭代状态,open:开始,separator:分割符,close:结束。
          -->
          
          <foreach collection="list" item="ids" index="index" 
                   open="(" separator="," close=")">
            #{ids}
          </foreach>
        </update>
        
      • 测试

        public static void main(String[] args) {
        		// TODO Auto-generated method stub
        		SqlSession session = MyBatisUtil.getSqlSession();
        		StaffMapper sDao = session.getMapper(StaffMapper.class);
        		
        		List<Integer> ids = new ArrayList<Integer>();
        		ids.add(1);
        		ids.add(2);
        		ids.add(3);
        
        		int num = sDao.updateById(ids);
        
        		session.commit();
          
        		session.close();
        }
        
    • sql片段
      对于字段数量较多的数据表,在编写查询SQL语句时使用“select *”需要谨慎一些。如果所需数据并非是所有字段,那么多余的字段数据导致数据文件I/O增加,另加额外消耗的网络带宽都会导致此次查询效率降低。因此把需要的字段列举在select子句中是正确的做法。如果某些字段在接口映射文件中出现次数较多,并且这些字段数量对于编码来说也较多,那么可以考虑把这些字段独立出来成为SQL脚本片段,从而重用该片段。
      
      • MyBatis在接口映射文件中提供了标签用于定义SQL脚本片段,使得该片段能够被重用。标签功能暂时没有对应的注解。

      • 标签配合使用

      • 配置

        <?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.tcc.dao.StaffMapper">
        
        	<!-- 用于sql语句的复用,里面最好不要写where语句,用<include>接收 -->
        	<sql id="if-name-dianZan">
        		<if test="name != null and name != ''">
        			 name=#{name},
        		</if>
        		<if test="dianZan != null">
        			dianZan=#{dianZan}
        		</if>
        	</sql>
        
        	<update id="update" parameterType="map">
        		update staff
        		<!-- set标签动态添加set 会把最后一个条件后面无用的“,”自动删除 -->
        		<set>
              <!-- 引入sql片段 -->
        			<include refid="if-name-dianZan"/>
        		</set>
        		where id=#{id}
        	</update>
        
        </mapper>
        
  • 缓存

    • 用户访问数据的顺序是二级缓存->一级缓存->从数据库拿数据
      • 先从二级缓存中查找数据,如果没有就从一级缓存中查,如果都没有再从数据库查
    • SqlSession对象的clearCache方法只会清除一级缓存,对二级缓存无效。并且会话对象在提交事务或者关闭时,才会把数据存储到二级缓存。
    缓存在数据库密集访问型的应用系统中被广泛采用,基本思想是,为了减少数据库的负荷,使用缓存存储热点数据,使得上层应用在不需要访问数据库的情况下通过缓存直接获得数据,对用户做出快速响应。缓存分为本地缓存和分布式缓存。如果缓存在应用系统内部,部署在应用系统所在的硬件服务器上,那么称此类缓存为本地缓存;反之,缓存是独立部署的,并且与应用系统不再相同的硬件服务器上,则称为分布式缓存。
    

    MyBatis本地缓存分为一级缓存和二级缓存。

    • 一级缓存
      • 一级缓存(PerpetualCache)是与SqlSession对象绑定的。随着会话对象关闭而销毁。SqlSession对象的clearCache方法能够对当前会话对象关联的缓存进行清除操作。另外,事务提交和数据更新操作(update、delete、insert)都会刷新一级缓存
    • 二级缓存
      • 二级缓存是跨多个会话的,MyBatis内置了一个二级缓存(PerpetualCache),可以直接使用。也可以使用其他缓存产品作为二级缓存,例如EhCache。如果选用的二级缓存产品是独立部署的,例如是Redis服务器,那么此时的二级缓存又是分布式缓存。

      • MyBatis二级缓存是跨会话的,也就是说一个会话的结束并不会清除二级缓存一个会话在内置的一级缓存中如果没有得到需要的数据,会进一步到二级缓存当中获取。如果二级缓存也没有命中,那么才到关系数据库中进行数据查询。这种缓存策略可以大量减少数据库的访问压力。

  • Mybatis注解式开发

    在具体使用MyBatis注解之前,需要辩证地看待注解。注解是和Java源代码混合在一起的,也就意味着修改了注解就是修改了源码,这就增加了编译、打包的频率。另外有些第三方库是闭源的,无法通过修改注解来改变第三方库的行为。鉴于此,在多数情况下,系统开发会选择混合使用XML和注解来提供配置信息。 
    
    • 创建接口UserMapper,不需要映射的配置文件(UserMapper.xml)了。

    • 接口

      @CacheNamespace(implementation = RedisCache.class) //使用分布式二级缓存Redis*/ //@CacheNamespace //使用Mybatis内置的默认二级缓存PerpetualCache 
      public interface UserMapper {  
        //多个键值对参数也可以用map传参,key对应列名,value对应列值  
        @Select(" SELECT * FROM USER WHERE name = #{name} and age = #{age}")  
        public List<User> findByNameAndAge(Map<String, Object> map); 
        
      } 
      
    • 修改配置文件mybatis-config.xml中mappers部分,如下代码所示:

      <mappers>  <package name="com.ysdit.dao"/> </mappers>
      
    • 元素使用了class属性来标识被注解了的映射接口,而不再是使用resource来指定接口映射文件。那么,SqlSessionFactoryBuilder会读取相关映射接口中的注解信息。

    • 如果对数据进行增、删、改操作,简单的SQL语句如下代码所示:

      @Select(value="select * from user where name=#{name}")  
      User queryUserByName(String name);  
      
      @Insert("insert into user (name,age) values(#{name},#{age})")  
      int insertUser(User user);  
      
      @Delete("delete from user where id=#{id}") 
      int deleteUserById(int id);  
      
      @Update("update user set name=#{name} where id=#{id}")  
      int updateUser(User user); 
      

SQL语句优化

  1. 尽量避免在where子句中使用is null比较,即使比较字段建立了索引,数据库也会放弃使用索引,因为索引中没有null值。所以在设计表时,尽可能不要有null字段。例如奖金字段,如果用null来表示未发放,可以考虑定义一个-1表示未发放

  2. 尽量避免在where子句中使用not、<>或者!=操作,否则数据库也会放弃使用索引。

  3. 尽量避免在where子句中使用or操作,如果有未建立索引的列参与or运算,数据库会放弃使用索引。可以考虑union合并结果。例如:

    select ename from emp where sal < 1000 or comm < 500; 
    

    可以替换为:

    select ename from emp where sal < 100 
    union 
    select ename from emp where comm < 500; 
    
  4. 尽量避免使用in、not in操作,数据库会放弃使用索引,可以用union、between…and或者exists替代。

  5. 尽量避免使用百分符号在前的like查询,如like ‘%abc%’。

  6. 尽量避免在where子句中对查询字段进行类型转换、函数计算和表达式计算,否则数据库会放弃使用索引。例如:

    select ename, sal from emp where sal+500 > 3000; 
    

    可以替换为,

    select ename, sal from emp where sal > 2500; 
    
  7. 谨慎使用复合索引,如果where子句中,条件字段不是复合索引的首字段,数据库会放弃使用索引。

  8. 尽量避免使用count(*),应该把主键字段作为count函数的参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值