阶段性总结---MyBatis

MyBatis框架

官方文档地址: https://mybatis.org/

什么是框架

​ 框架类似于脚手架,建筑时候帮助你更快的完成建造任务,单纯的框架不能用来当房子用,但是辅助盖楼也是很有必要的,能够节省更多的时间。另一种说法就是框架提供了一系列的模板代码,只需要很少的代码来实现基础功能,最早时候通过JDBC连接数据库,每次都写加载驱动,连接数据库,写SQL语句,执行语句,处理结果集,关闭连接,多麻烦呀,所以框架就提供了一个入口,写上驱动名和相关的参数就能进行SQL操作了。

​ 我还有一层理解,就是框架中集成了大量的代码,通过反射等各种方式,对性能有些影响,但是对于后期维护起来更加方便,所以这个问题能难为到我这种强迫症,毕竟现在都是框架横行的时代,不用也不行啊,所以权衡性能和维护难度吧。【这里是我的疑问,大佬可以帮我解答以下】

软件的三层架构

  1. 数据持久化存储层:主要和数据库打交道的
  2. 业务层:主要实现业务功能的
  3. 表现层:与用户进行交互的,并且能与业务层联系

DAO层与业务层联系,业务层与表现层联系,这样的架构应该是目前主流的规范。

MVC设计思想

​ 最早我对三层构架和MVC模型总是混淆,MVC是一种设计思想,他们确实有相似的地方,Model 是模型,模型拥有多种处理任务,模型可以对多个视图提供数据,可以有效的减少代码量。View是视图,与前端页面关联最大,一般都是由网页或者是客户端界面组成。Controller是控制器,控制器接收用户请求,并且完成对模型的指向,

MyBatis入门

​ MyBatis是一个持久层框架,提供了与数据库交互的方法,能够减少大量代码,主要写一些SQL语句就可以了,通过XML配置,这样维护起来也更加方便了,与代码混淆在一起就会感到很臃肿。

MyBatis依赖

​ MyBatis虽然可以操作数据库,但是也需要MySQL的驱动支持,所以在使用MyBatis时,仍要导入MySQL的驱动,以Maven项目为例,依赖如下所示:

<dependencies>
    <!--mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
</dependencies>

MyBatis核心配置

​ MyBatis需要一个主配置文件,这个文件中包含了与数据库连接的配置以及映射文件的配置,所以核心配置文件不能丢掉。

<?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>
    <environments default="development"> <!--可以设置多个环境,选择的时development-->
        <environment id="development"> <!--development环境-->
            <transactionManager type="JDBC"/> <!--事务处理方式为JDBC-->
            <dataSource type="POOLED"> <!--通过池连接-->
                <property name="driver" value="com.mysql.jdbc.Driver"/> <!--MySQL驱动全限定名-->
                <property name="url" value="jdbc:mysql:///db_mybatis"/> <!--数据库链接地址-->
                <property name="username" value="root"/><!--用户名-->
                <property name="password" value="root"/><!--密码-->
            </dataSource>
        </environment>
    </environments>
    <mappers> <!--映射文件配置-->
        <!--单一文件-->
        <mapper resource="UserMapper.xml"/>
        <!--扫描包下的映射文件-->
        <package name="com.alc.mapper"/>
        <!--当你没有写xml,使用的时实现类,用这个-->
        <mapper class="com.alc.DoctorMapper"/>
    </mappers>
</configuration>

核心配置文件标签

在资源目录创建一个数据库连接的配置文件:文件名(db.properties)

db.drvierName=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///db
db.username=root
db.password=root

核心配置文件中读取上面的配置文件

<properties resource="db.properties"></properties>
<!--读取上面配置文件-->
<dataSource type="POOLED">
    <!--property name值为固定的, value:可以根据需求自定义-->
    <!--获取值的方法也和el表达式类似-->
    <property name="driver" value="${db.drvierName}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</dataSource>

映射文件

<?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="userMapper"> <!--代表命名空间,目前理解成一个类就行了,后期与接口对接,直接写个接口不用写实现类就能执行SQL语句了-->
    <select id="selectUser" resultType="com.alc.entity.User">  <!-- select 选择标签, id为唯一标识,与接口名对应 resultType为返回值类型-->
        select * from user
    </select>
    <!--insert:添加标签;parameterType:参数类型-->
    <insert id="addUser" parameterType="com.alc.entity.User">  <!--参数类型为User,也就是User中的变量名,一定要有get方法嗷-->
        insert into user values(#{id},#{name},#{age},#{sex},#{birthday})
    </insert>
</mapper>

类型的别名

​ 就像上面的resultType写的都是全限定名,每次这么写太累了,所以可以给他们起个别名。在核心配置文件中,添加别名标签和属性。如下所示:

<typeAliases>
    <!--type:类的全限定名;alias:别名-->
    <typeAlias type="com.alc.entity.User" alias="u"/>
</typeAliases>

​ 这样就可以在映射文件中直接使用u来代表我的User类了。

<select id="selectById" resultType="u" parameterType="java.lang.Integer">
    select * from user where id=#{id}
</select>

迫不及待的运行

maven提供了测试类,我们可以在测试类中写代码,看一下是否能够执行呢?[当前没有使用接口方式,只有核心配置文件、映射文件和实体类]

@Test
public void query() throws IOException {
    //alt+enter:补全返回值类型
    //读取mybatis的核心配置文件
    InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml");  
    //获取SqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
    //获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //args:映射文件中的namespace.操作标签的id值
    List<User> list = sqlSession.selectList("userMapper.selectUser");
    //输出结果
    list.forEach(System.out::print);
    //关闭连接对象
    sqlSession.close();
}
@Test
public void add() throws IOException {

    //读取mybatis的核心配置文件
    InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    User u=new User();
    u.setName("Matrix");
    u.setAge(23);
    u.setSex("男");
    u.setBirthday(LocalDate.now());

    //args1:映射文件中的namespace.操作标签的id值;args2:保存的对象
    sqlSession.insert("userMapper.addUser",u);
    //提交事务
    sqlSession.commit();
    sqlSession.close();
}

小任务

​ 如果多次操作数据库,读取配置文件,创建SqlSessionFactory代码也挺繁琐的,可不可以提取一个工具类出来呢?

CURD

标签

  1. SELECT
  2. UPDATE
  3. INSERT
  4. DELETE

​ 也就是标签名为这些,这些标识告诉MyBatis你要执行的操作是什么,从而对结果集进行处理,例如:

<mapper namespace="userMapper">
    <!--select:查询标签; id:唯一标识; resultType:返回结果类型-->
    <select id="selectUser" resultType="com.alc.entity.User">
        /*sql语句*/
        select * from user
    </select>
    <!--insert:添加标签;parameterType:参数类型-->
    <insert id="addUser" parameterType="com.alc.entity.User">
        insert into user values(#{id},#{name},#{age},#{sex},#{birthday})
    </insert>

    <!--update:更新-->
    <update id="updateUser" parameterType="com.alc.entity.User">
        update user set name=#{name},age=#{age},sex=#{sex},birthday=#{birthday} where id=#{id}
    </update>

    <!--delete:删除-->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id=#{id}
    </delete>

    <!--根据主键查询某一条数据-->
    <select id="selectById" resultType="com.alc.entity.User" parameterType="java.lang.Integer">
         select * from user where id=#{id}
    </select>
</mapper>

基于接口开发

基于接口开发,不需要写实现类,直接写xml就可以了,减少了代码量,但是也有他的规范,规范如下。

  1. 映射文件中的namespace属性值==接口类的全限定名
  2. 映射文件中CRUD标签中的id属性与接口类方法名一致
  3. 映射文件中的参数类型和返回值类型要与方法一致

MyBatis-API

  • Resources 通过调用此方法可以读取到核心配置文件
    • Resources.getResourceAsStream(“myBatisConfig.xml”)
  • SqlSessionFactory 通过工厂对象创建连接对象
    • SqlSessionFactory.openSession();
  • SqlSession 进行语句执行的实例方法
    • SqlSession.* * *

理解:核心配置文件相当于资金,拿着钱买了个工厂,工厂的参数就是钱,现在由工厂了就开启流水线,流水线相当于一个产品(SqlSession),然后让产品去做他该做的事。

ResultMap

​ 当实体类和数据库中字段不匹配时,需要添加对应法则,告诉MyBatis那个列对应的时那个变量。

<resultMap id="userInfo" type="u">
    <!--result:结果; property:实体类中的属性名;column:查询的字段名-->
    <result property="sname" column="name"/>
</resultMap>

<select id="selectById" resultMap="userInfo">
    select * from user where id=#{id}
</select>

多参数查询

通常情况下,我们的查询语句都附加了条件,有些可能是多个,比如登录,我们有一个实体类User,其中有两个参数,一个是username,另一个是password, 这样我们可以封装到实体类中进行sql查询,(user.username user.password)这样的方式,不难看出,这样的方式只有一个参数,这个参数是实体对象,但是如果查询参数不具有实体类或者是不想封装,也可以通过多种参数的方式进行查询。

我们开启这个项目,需要导入依赖文件,并且配置MyBatis的核心配置文件,数据库部分就不过多介绍了,官方网站上有中文解释,文章前也有相关的模板。在这里呢,我们在映射标签中需要定义mappers,因为项目中很少有一个映射,所以建议试用package进行扫描,这样就不需要后期的一个一个的添加了,也避免了忘记添加映射而无法正常运行。

<mappers>
    <package name="com.alc.mapper"/>
</mappers>

例如: 根据姓名和年龄查询用户信息;这时候,我们分析一下我们需要那些参数和返回值类型。返回值类型是一个List列表,所以接口需要以List进行定义,而参数有多种方法定义。如下所示:

//1.根据性别与年龄查询用户信息
List<User>selectBySexAndAge(String sex,Integer age);
//2.根据性别与年龄查询用户信息
List<User>selectBySexAndAge(User user);
//3.根据性别与年龄查询用户信息
List<User>selectBySexAndAge(Map<String,Object>map);
//4.根据性别与年龄查询用户信息
List<User>selectBySexAndAge(@Param("sex") String sex, @Param("age") Integer age);

本文就以这样的接口定义,这里给出了四种方式,路径为com.alc.mapper.UserMapper,这里只提供了映射接口,但是需要映射文件,也就是xml文件,需要在resources文件中添加相同的路径,(这里于IDEA 的编译方式有关,如果可以编译java文件夹下的非编译文件,就可以放在java文件夹下了,为了规范,我们需要在resources文件夹下定义)。

目录定义如下所示嗷

-java
|-com
|-alc
|-mapper
|-UserMapper.java
|-UserMapper.xml # 如果这样,需要添加编译目录,不符合规范,不建议这么做

-resources
|-com
|-alc
|-mapper
|-UserMapper.xml # 注意 目录的定义要与映射java文件的目录一致。。。

然后我们就到了关键的映射定义部分了。

<?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.alc.mapper.UserMapper">

    <select id="selectBySexAndAge1" resultType="com.alc.entity.User"> <!-- arg0 表示第一个参数 param1 表示第一个参数 以此类推 -->
        SELECT * FROM user
        <where>
            sex = #{arg0} and age = #{param2}
        </where>
    </select>
    <select id="selectBySexAndAge2" resultType="com.alc.entity.User">
        SELECT * FROM user
        <where>
            sex = #{sex} and age = #{age}
        </where>
    </select>
    <select id="selectBySexAndAge3" resultType="com.alc.entity.User">
        SELECT * FROM user
        <where>
            sex = #{sex} and age = #{age}
        </where>
    </select>
    <select id="selectBySexAndAge4" resultType="com.alc.entity.User">
        SELECT * FROM user
        <where>
            sex = #{sex} and age = #{age}
        </where>
    </select>
</mapper>

测试类如下所示:建议对上面的代码进行截图,以贴图的方式,进行对比,这样更容易理解这些代码了。

import com.alc.entity.User;
import com.alc.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyBatisTest {
    private static UserMapper mapper = null;
    static {
        try {
            InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
            SqlSession session = build.openSession();
            mapper = session.getMapper(UserMapper.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    } // 整合以下模板代码,测试的时候直接试用就好了。
    @Test
    public void T1(){
        List<User> rst = mapper.selectBySexAndAge1("男", 38);
        //直接通过参数索引获取,所以不需要特殊处理。
        rst.forEach(System.out::println);
        System.out.println("T1");
    }
    @Test
    public void T2(){
        User user = new User();
        user.setSex("男");user.setAge(38);
        //因为封装到实体对象中了,所以需要传递一个对象过去,具体内容存放在在该对象中
        List<User> rst = mapper.selectBySexAndAge2(user);
        rst.forEach(System.out::println);
        System.out.println("T2");
    }
    @Test
    public void T3(){
        Map params = new HashMap();
        params.put("sex","女");params.put("age",28);
        //以map集合的方式存储查询参数,我们都知道,map集合的key是唯一的,所以mybatis会解析map,对其中的参数进行查询
        List rst = mapper.selectBySexAndAge3(params);
        rst.forEach(System.out::println);
        System.out.println("T3");
    }
    @Test
    public void T4(){
        List<User> rst = mapper.selectBySexAndAge4("女", 28);
        //通过别名去取,也就是接口中的@Param中的值,不妨改变以下试试看。
        rst.forEach(System.out::println);
        System.out.println("T4");
    }
}
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running MyBatisTest
User(id=1, name=����, age=38, sex=��, birthday=Wed Nov 11 11:12:13 CST 2020)
T1
User(id=1, name=����, age=38, sex=��, birthday=Wed Nov 11 11:12:13 CST 2020)
T2
User(id=2, name=ij��ʦ, age=28, sex=Ů, birthday=Mon Dec 21 14:32:12 CST 2020)
T3
User(id=2, name=ij��ʦ, age=28, sex=Ů, birthday=Mon Dec 21 14:32:12 CST 2020)
T4
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.633 sec

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

已经通过了。

模糊查询

​ 映射xml中需要做调整,我们经过上面的代码不难看出来,mybatis也就是进行字符串的拼接,但是有没有发现,我们对参数的传入都是通过‘#’这个符号进行拼接的,在这里引入一个知识点,’#’ 这个符号是对sql语句进行预编译的,MyBatis将会转义其中的字符,如果使用’$'符号,将会当作字符串来处理,不会进行预编译。

<select id="selectLikeName" resultType="com.alc.entity.User">
    select * from user where name like #{name}
</select> <!--这种方式需要在java代码中手动添加'%' 不是很方便,但是可以解决模糊查询的问题-->
<select id="selectLikeName" resultType="com.alc.entity.User">
    select * from user where name like "%"#{name}"%"
</select> <!--这种方式看起来很奇怪,但是mysql可以使用,因为没有用过Oracle,不知道Oracle是否支持这种方式-->
<select id="selectLikeName" resultType="com.alc.entity.User">
    select * from user where name like '%${name}%'
</select><!--进行sql语句的拼接,存在sql注入问题-->
<select id="selectLikeName" resultType="com.alc.entity.User">
    select * from user where name like CONCAT(CONCAT('%',#{name}),'%')
</select><!--这种方式是使用了SQL中的字符串拼接函数,CONCAT(AC,DB)==>"ACBD"-->

Log4j

​ 在SQL执行过程中,我们可以通过日志对执行过程进行查看,这里可以使用log4j来实现这个操作

  • 添加依赖

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>2.0.0-alpha1</version>
    </dependency>
    
  • log4j.properties 配置

    log4j.rootLogger=info,stdout,D
    
    ### 输出到控制台 ###
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}%5p%c{1}:%L-%m%n
    
    ### 输出到日志文件 ###
    log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.D.File=D:/logs/log.log
    log4j.appender.D.Append=true
    log4j.appender.D.Threshold=DEBUG
    log4j.appender.D.layout=org.apache.log4j.PatternLayout
    log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]-[%p]%m%n
    

    ⚠️ 文件名不能变,只能是log4j.properties

  • 配置mybatis核心配置文件 (添加这条配置)

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    

获取添加数据信息

​ 想象一个场景,在高并发的情况下,多人操作数据库,我们向用户表中插入一条数据,由于ID是自动生成,我们如果需要这个ID,进行其他表中的关联操作,这就需要在执行完成后对ID进行回取。

​ 以上一个场景为例,我们需要在xml中进行设置,把并且id返回给我们的java程序,代码如下所示。

<!--useGeneratedKeys:true:支持在添加数据的时候,获取注解的值;
    keyProperty:将查询到主键值赋值给某一个属性
    -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user VALUE(NULL,#{name},#{password},#{sex},#{age},#{birthday})
</insert>

​ 通过上面的代码我们可以看到,insert添加了两个属性,并且缺少了parameterType属性,parameterType这个属性一般情况是可以省略不写的,但是为了后期维护的便捷以及见名知意,还是写上比较好,mybatis会自动识别,但是为了规范以及代码的可读性,还是写上比较合理。

​ 看到这里,我们还没有获取到id值,实际上这里只是对于配置的书写,然而获取仍需要在java代码中实现,比如我们要向用户表中添加一条数据。

User u = new User(); //定义一个用户实体对象
u.setName("老崔");
u.setSex("男");
u.setBirthday(new Date());
u.setPassword("123");
u.setAge(34);
//上面我们没有对ID进行赋值。
Integer rst = mapper.insertUser(u); // 调用添加接口映射
System.out.println(rst + "," + u.getId()); //rst为1代表插入成功,id为刚刚添加数据的id值,切记,前提是id字段自动增长
session.commit();//最后不要忘记提交嗷。

根据日志文件,我们分析一下。

2021-03-28T11:22:15.793612Z        11 Connect   study@localhost on study using TCP/IP
2021-03-28T11:22:15.798292Z        11 Query     /* mysql-connector-java-5.1.49 ( Revision: ad86f36e100e104cd926c6b81c8cab9565750116 ) */SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout
2021-03-28T11:22:15.820716Z        11 Query     SET character_set_results = NULL
2021-03-28T11:22:15.821021Z        11 Query     SET autocommit=1
2021-03-28T11:22:15.825516Z        11 Query     SET autocommit=0 # 默认是手动提交(手动事务管理)
2021-03-28T11:22:15.842043Z        11 Query     select @@session.transaction_read_only
2021-03-28T11:22:15.842416Z        11 Query     INSERT INTO user VALUE(NULL,'老崔','123','男',34,'2021-03-28 19:22:15.57')  # 添加
2021-03-28T11:22:15.846378Z        11 Query     commit

在上面没有进行执行 SELECT LAST_INSERT_ID(); 这条语句。还有另一种方法,但是代码量稍微大一点,并且我个人认为不太严谨,因此就仅以介绍,不做详解。

<!--
    keyProperty:查询到的主键值赋值给哪一个属性名;
    keyColumn:查询的主键对应的表中的字段名
    resultType:查询的主键的数据类型
    order: BEFORE/AFTER在进行添加动作之前还是之后进行查询
    -->
<insert id="add" parameterType="u">
    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
        select LAST_INSERT_ID()
    </selectKey>
    insert into user values(#{id},#{name},#{password},#{age},#{sex},#{birthday})
</insert>

再精简一点

​ 当我们进行查询或者其他操作的时候,还是需要一些模板SQL语句,当然,有些时候参数也是模板代码,所以可以使用sql标签。

<sql id="sel">
	SELECT * FROM user
</sql>

​ 但是我们定义完了,如何去使用呢?

<****(select) id="***">
    <include refid="sel"/> where ***
</****(select)>

高级映射

表与表的关联关系

  • 多对一
  • 一对一
  • 一对多
  • 多对多

​ 多对一和一对多,我的理解是相对的,例如老师与学生之间的关系,一个老师教多个学生,多个学生被一个老师教,一对多,多对一,是参照关系的不同,因此不要对高级映射关系有畏惧之心。

多对一

​ 以学生作为"参照物",一个学生对应多个老师,因此再定义学生类的时候,需要把老师定义在学生中,数据表中,学生中要包含老师的id值,这样就存在了对应关系了。

​ 映射文件的配置(方式一):

学生映射

<resultMap id="stuResult" type="com.alc.entity.Student">
    <id property="id" column="sid"/>
    <result property="name" column="sname"></result>
    <result property="sex" column="ssex"></result>
    <result property="age" column="sage"></result>
    <!--association:多对一/一对一的配置
        javaType:设置当前这个属性的java类型-->
    <association property="teacher" javaType="com.alc.entity.Teacher">
        <id property="id" column="tid"/>
        <result property="name" column="tname"></result>
        <result property="sex" column="tsex"></result>
        <result property="age" column="tage"></result>
    </association>
</resultMap>
<select id="findById" resultMap="stuResult">
    SELECT
        s.id sid,
        s.NAME sname,
        s.sex ssex,
        s.age sage,
        t.id tid,
        t.NAME tname,
        t.sex tsex,
        t.age tage 
    FROM
        student s,
        teacher t 
    WHERE
        s.teacher_fk = t.id 
        AND s.id = #{id}
</select>

方式二:

学生映射

<resultMap id="stuResult" type="com.alc.entity.Student">
    <!--多对一配置
        association:多对一关联配置
        property:实体类中起关联作用的属性名
        column:查询到的数据库表中起关联作用的字段名
        select:调用在别的mapper中定义的查询: namespace.id
        -->
    <association property="teacher" column="teacher_fk"        select="com.alc.mapper.TeacherMapper.findById"/>
</resultMap>
<select id="findById" resultMap="stuResult">
    select * from student where id=#{id}
</select>

老师映射

<mapper namespace="com.alc.mapper.TeacherMapper">
    <select id="findById" resultType="com.alc.entity.Teacher">
        select * from teacher where id=#{id}
    </select>
</mapper>

​ 相比来说,两种方式都可以实现相同的作用,但是明显后者SQL语句比较少,前者但是更能体现出其中的关系。

一对多

​ 根据上面的多对一,我们依然可以通过老师与学生之间的关系产生一对多的模型,一个老师对应多个学生,表结构不需要改变,但是在实体定义的时候,老师中的学生需要通过一个列表的方式存储,也就是说,在老师的实体中,包含其所教的所有学生信息。

​ 这样的方式,是以老师为参照物的。

​ 实现方式一:(老师映射)

<resultMap id="teacherResult" type="com.alc.one2many.Teacher">
    <id property="id" column="tid"/>
    <result property="name" column="tname"></result>
    <result property="sex" column="tsex"></result>
    <result property="age" column="tage"></result>
    <!--
        collection:对多配置
        ofType:集合中存放的元素数据类型
        -->
    <collection property="students" ofType="com.alc.one2many.Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"></result>
        <result property="sex" column="ssex"></result>
        <result property="age" column="sage"></result>
    </collection>
</resultMap>
<select id="findById" resultMap="teacherResult">
    SELECT
        s.id sid,
        s.NAME sname,
        s.sex ssex,
        s.age sage,
        t.id tid,
        t.NAME tname,
        t.sex tsex,
        t.age tage 
    FROM
        student s,
        teacher t 
    WHERE
        s.teacher_fk = t.id 
        AND t.id = #{id}
</select>

​ 实现方式二:(老师映射)

<resultMap id="teacherResult" type="com.alc.one2many.Teacher">
    <!--select:调用StudentMapper中定义的findByTeacherId方法-->
    <collection property="students" column="id" select="com.alc.dao.StudentMapper.findByTeacherId"></collection>
</resultMap>
<select id="findById" resultMap="teacherResult">
    select * from teacher where id=#{id}
</select>

学生映射

<mapper namespace="com.alc.dao.StudentMapper">
    <select id="findByTeacherId" resultType="com.alc.one2many.Student">
        select * from student where teacher_fk=#{id}
    </select>
</mapper>

多对多

​ 这层概念有些混乱,但是,简单的说,多对多就是一对多和多对一的叠加版,以商城系统为例,一个订单可以对应多个商品,订单详情中包含订单和商品信息,一个商品可以存在于多个订单中,一个订单也可以包含多个商品。

​ 实际中用的不是很多,其实我很不喜欢这样的复杂查询,我认为DAO层就应该是一些简单的SQL查询,其余的由服务层处理。

动态SQL

  • if 标签

    • 属性:test

      • 值:条件
    •  <if test="name !=null and name !=''">
           where name like #{name}
       </if>
      
  • choose标签

    • 属性:无

    • 子标签 :when

      • 属性:test
      • 值:条件
    • <when test="sex!=null and sex!=''">
          sex=#{sex}
      </when>
      <when test="age!=null">
          age>#{age}
      </when>
      <otherwise> <!--相当于switch中的default-->
          1=1
      </otherwise>
      
  • where 标签

    • 属性:无

    • 子标签:无

    • 作用:自动填充where

    • select * from user
      <where>
          <if test="name !=null and name !=''">
              name like #{name}
          </if>
      </where>
      

      如果测试条件成立,则添加where

      如果不成立,就不添加where

  • set标签

    • 属性:无

    • 子标签:无

    • 作用:在更新时根据字段添加逗号,不需要手动添加,

    • update user
      <set>
          <if test="name!=null">
              name=#{name}
          </if>
          <if test="age!=null">
              age=#{age}
          </if>
          <if test="sex!=null">
              sex=#{Sex}
          </if>
      </set>
      where id=#{id}
      
  • foreach标签

    • 遍历传入的参数,生成长语句,类似于批量操作

    • 属性:collection

      • 值:array 表示传入的是数组
      • 值:collection 表示传入的是集合(Map)
    • 属性:item

      • 值:遍历过程中单一元素存放的变量
    • 属性:open

      • 值:遍历前添加的字串
    • 属性:close

      • 值:遍历后添加的字串
    • 属性:separator

      • 值:每次遍历中间分割的符号
    • <insert id="batchInsert">
          /*批量数据添加*/
          insert into user values
          <foreach collection="collection" item="user" open="(" close=")" separator="),(">
              #{user.id},#{user.name},#{user.age},#{user.sex},#{user.birthday}
          </foreach>
      </insert>
      

注解形式

​ 不建议使用这种方式,毕竟java文件太长的话,维护起来还是比较麻烦,并且编译在class中。

​ 方法就是在映射接口中添加注解标签,语句放在注解里,如下所示。

@Select("SELECT * FROM user WHERE id = #{id}")
User findById(int id);

​ 相应的也有Update Delete Insert标签。

​ 当进行多表查询时,还需要借助另一个标签Results,其中分为一对多和多对一两种场景。接下来分别演示。

一对多

学生映射文件:

//根据教师id查询学生信息
@Select("select * from student where teacher_fk=#{id}")
List<Student>selectByTeacherId(int id);

教师映射文件:

//根据教师的id查询教师信息及其对应的学生信息
 @Select("select * from teacher where id=#{id}")
 @Results(value={
     @Result(id = true,property = "id",column = "id"),//@Result(id = true)(表示这个值是表中的主键)
     @Result(property ="name" ,column ="name"), //<result property=""  column=""></result>
     @Result(property ="sex" ,column ="sex"),
     @Result(property ="age" ,column ="age"),
     @Result(property = "students",column = "id",
     many =@Many(select = "com.alc.mapper.StudentMapper.selectByTeacherId"),fetchType=FetchType.LAZY)
         })
 Teacher selectById(int id);

多对一

学生映射文件:

//根据教师id查询学生信息
@Select("select * from student where id=#{id}")
@Results(value={
    @Result(id = true,property = "id",column = "id"),
    @Result(property ="name" ,column ="name"), 
    @Result(property ="sex" ,column ="sex"),
    @Result(property ="age" ,column ="age"),
    @Result(property = "teacher",column = "id",
            one =@One(select = "com.alc.mapper.TeacherMapper.selectById"),fetchType=FetchType.LAZY)
})
Student QueryById(int id);

教师映射文件:

//根据教师的id查询教师信息及其对应的学生信息
 @Select("select * from teacher where id=#{id}")
 Teacher selectById(int id);

一级缓存

​ 默认情况下,一级缓存是打开的,当进行查询操作时,将会把结果存放在缓存中,当进行增删改操作,会清空缓存中的数据,避免总是查询数据库,降低系统效率。实际上,缓存中的数据存放在sqlSession对象中。

二级缓存

​ 二级缓存默认关闭,如果开启,需要在核心配置文件中的settings标签中定义cacheEnabled为true,还需要在映射文件中添加<cache/>标签,二级缓存通常存放一些不经常改变的数据。通俗的说,他们作用域不太一样,一级缓存在sqlsession对象中,而二级缓存对当前的映射生效。当某一条语句不希望使用缓存,则在映射标签中添加属性useCache为false。

延迟加载(懒加载)

​ 当进行多表查询时,有些情况下时不需要即使查询第二张表,如果数据量较大,就会降低用户体验,当用户需要第二张表数据时,再进行查询。

​ 应用场景:用户实体中存在该用户的部门信息,但是页面中只有在查看详情以及编辑时需要,这个时候就可以使用懒加载策略。

<resultMap id="UserResult" type="com.alc.one2many.User">
    <collection property="department" column="did" select="com.alc.dao.DeptMapper.findByDid" fetchType="lazy">
    </collection>
</resultMap>
<select id="findById" resultMap="UserResult">
    select * from user where id=#{id}
</select>
<!--fetchType:默认的情况下是eager,如果设置为lazy,就会起到延迟记载的效果-->

逆向工程

​ MyBatis提供了逆向工程,逆向工程可以通过数据库的连接,自动生成实体类、映射文件、以及相应的实现方法。

以下是实现逆向的依赖文件

<dependencies>
    <!--逆向工程需要的jar-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.7</version>
    </dependency>
    <!--mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--junit测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

以下是配置文件

<?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>
 <context id="DB2Tables" targetRuntime="MyBatis3">
		<!--实体类实现序列化接口-->
     <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
     <!--生成实体类中的toString方法-->
     <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
     <commentGenerator>
         <!--关闭注释-->
         <property name="suppressAllComments" value="true"></property>
     </commentGenerator>
     <!-- 设定数据库连接 -->
     <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                     connectionURL="jdbc:mysql://localhost:3306/db_mybatis"
                     userId="root"
                     password="root">
     </jdbcConnection>

     <!--  生成 实体类 存放的位置  -->
     <javaModelGenerator targetPackage="com.alc.entity" targetProject=".\src\main\java">
         <property name="enableSubPackages" value="true" />
         <property name="trimStrings" value="true" />
     </javaModelGenerator>

     <!-- 生成的映射文件的位置 -->
     <sqlMapGenerator targetPackage="com.alc.mapper"  targetProject=".\src\main\resources">
         <property name="enableSubPackages" value="true" />
     </sqlMapGenerator>

     <!-- 生成的接口的存放位置  -->
     <javaClientGenerator type="XMLMAPPER" targetPackage="com.alc.mapper"  targetProject=".\src\main\java">
         <property name="enableSubPackages" value="true" />
     </javaClientGenerator>

     <!--  设定反向生成的表 -->
     <table tableName="goods"></table>
     <table tableName="student"></table>
     <table tableName="teacher"></table>
     <table tableName="user"></table>
 </context>
</generatorConfiguration>

以下是测试文件

@Test
public void generator() {
    try {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("配置文件路径");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator MyBatisGenerator =
            new MyBatisGenerator(config, callback, warnings);
        MyBatisGenerator.generate(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

异常

如果爆BindingException异常,检查下面的配置是否正确。

  1. 接口文件名和映射文件名是否一致
  2. 不需要写实现类,按照基于接口开发的规则
  3. 映射文件路径和java接口路径是否一致
  4. 是否在resources下创建的文件夹,不要习惯性的以’.'创建层级结构,资源文件中不能以‘.’作为目录分割
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值