MyBatis的使用方法

XML配置形式用法

https://blog.csdn.net/hrnne/article/details/106034494

注解形式的用法

直接在接口文件中添加SQL语句
@Select

@Select("select * from Student where id = #{id}")
public Student selectStudentById(int id);

@Insert
@Update
@Delete
同上

多个参数的传递

通过学生年龄和性别来查询学生信息
select * from Student where Sage=? and Ssex=?
mybatis配置如下:

@Select(select * from student where age=#{age} and sex=#{sex})
public getStudentByAgeAndSex(int age, String sex);

在这里插入图片描述
XML可用的参数只有0,1,param1和Param2,Mybatis根据位置自定义的名字,可以将#{sex}改为#{0}或者#{param1}
多参数传递是给参数配置@Param注解

public Student getStudentByAgeAndSex(@Param("age") int age, @Param("sex") String sex);

当结果有多条记录时,加上List<>

public List<Student> getStudentByAgeAndSex(@Param("age") int age, @Param("sex") String sex);

配置注解后,mybatis自动的将参数封装成Map类型,@Param注解值作为Map中的key,参数值作为Map中的value,通过这种形式可以识别多参数的对应关系>

#{}和${}的区别

{}的使用

@Select("select * from Student where SID=#{sid}")
public Student getStudentBySID(int sid);

日志打印:

Preparing: select * from Student where SID=? 
Parameters: 2(Integer)

#{}占位符在执行过程中将#{}替换成 ? 占位符,将参数值和SQL分开传递到服务器。
#{}使用类似于JDBC编程中preparedStatment

${}的使用

在这里插入图片描述
参数的获取使用ognl表达式,使用的参数必须提供getteer方法

@Select("select * from Student where SID=${SID}")
public Student getStudentBySID(Student student);

打印日志:

Preparing: select * from Student where SID=1 
Parameters:

使用${}方式传递参数时,直接将参数拼接到SQL。
${}使用JDBC编程中的Statement的操作。

#{}不存在SQL注入问题,采用预编译机制,将SQL和参数分别传递给SQL服务器,而${}存在SQL注入问题,所以一般情况时使用#{}。

动态SQL

Mybatis的强大特征之一 在于动态SQL,采用ognl表达式来处理SQL,根据表达式的不同,能将SQL进行拼接和组装。
主要的动态SQL标签:if,where,trim(where,set),foreach。

if标签

根据年龄和性别查询学生

<!--
    if表达式一般放在where条件后,判断蚕食是否传递使用if test属性(必填)为true或false
    test使用OGNL表达式处理,返回true则进入到if标签的SQL,否则不会进入
    参数处理:
    1)都不传:select * from student where  !!!要添加1=1
    2)都传:select * from student where age = #{age} sex = #{sex}   !!!要加and
    3)只传age:select * from student where 1=1 and age = #{age}
    4)只传sex:select * from student where 1=1 and sex = #{sex}
    -->
    <select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
        select * from student where 1=1
        <if test="age != 0 and age != null" >
            and age = #{age}
        </if>
        <if test="sex != null"  >
            and sex = #{sex}
        </if>
    </select>

测试:

  1. 都传
    在这里插入图片描述
  2. 只传age
    在这里插入图片描述
  3. 只传sex
    在这里插入图片描述
  4. 都不传
    在这里插入图片描述

where标签

where标签:一般和if标签一块使用,如果标签包含的元素有返回值就插入where,将紧随where后面的And或OR开头的,将他们剔除

    <!--where标签
    1)只传age:select * from Student where and(会自动剔除) age = #{age}
    2)都不传:select * from student
    3)都传:select * from student where age = #{age} and sex = #{sex}
    -->
    <select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
        select * from student
        <where>
            <if test="age != 0 and age != null" >
                and age = #{age}
            </if>
            <if test="sex != null"  >
                and sex = #{sex}
            </if>
        </where>
    </select>

测试:与if相同。

trim标签(where/set)标签

trim标签用于取出SQL中多余的and关键字、逗号,使用在where或set中。

属性描述
prefix给SQL语句拼接的前缀
suffix给SQL语句拼接的后缀
prefixOverrides去除SQL语句前的关键字或字符
suffixOverrides去除SQL语句后的关键字或字符
    <!--trim标签(结合where)
    作用等同于直接使用where
    -->
    <select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
        select * from student
        <trim prefix="where" prefixOverrides="and">
            <if test="age != 0 and age != null" >
                and age = #{age}
            </if>
            <if test="sex != null"  >
                and sex = #{sex}
            </if>
        </trim>
    </select>

foreach标签

批量处理(插入,查询等)
场景:通过一批ID查询用户信息
select * from Student where id in(1,2,3)
insert into Student (id,name) values(20,“zhangsan”),(21,“lisi”),(22,“wangwu”)

接口文件的方法:

public List<Student> batchSelectStudentByIds(List<Integer> ids);

Mapper.xml文件

    <!--批量查询SQL
    foreach表达式:
    collection(必填):指定输入参数类型。
        list:列表。array:数组。map:map集合。
    item:给集合中单个元素的名称
    open:开始的字符串。close:结束的字符串。
    separator:数据之间的分隔符。
    -->
    <select id="batchSelectStudentByIds" resultType="student">
        select * from Student where sid in
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>

测试:
在这里插入图片描述

模糊匹配

需求:查询Student表,表中含有"明”的所有用户
SQL:select * from Student where Sname like “%明%”;
方式1:直接在参数上拼接通配符
Mapper.xml配置:在这里插入图片描述
测试用例:
在这里插入图片描述
日志打印:
在这里插入图片描述
方法2:Mysql中的concat(,)
concat(par1,par2) :进行字符串拼接。
Mapper.xml配置:
在这里插入图片描述
测试用例:
在这里插入图片描述
日志打印:
在这里插入图片描述
方法3:bind表达式处理
Mapper.xml配置:
在这里插入图片描述
接口方法:在这里插入图片描述
测试用例:
在这里插入图片描述
日志打印:
在这里插入图片描述

动态代理:

原生的方法调用直接通过sqlSession方法调用:提供了selectOne,selectList,Insert,delete…方法
返回多个结果时,使用selectList,返回的结果不管是单个还是多个在resultType属性都是返回的Java对象全路径。返回单个结果对象使用selectOne。sqlSession.selectList(“com.tulun.dao.StudentMapper1.getStudentsByName”, “%L%”);

  • 代理模式是Java中的一种设计模式。
  • 代理模式中代理类和委托类具有相同的接口
  • 代理类的主要职责是为委托类预处理消息,过滤消息等功能。
  • 代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相- 关方法,来提供特定的一些服务。
  • 代理类和委托类之间存在关联关系,一个代理类对象和一个委托类的对象相关联。
  • 访问实际对象,是通过代理类方法的。

代理模式的话分为静态代理和动态代理。

  • 静态代理是在程序编译阶段确定代理对象。
  • 动态代理是在程序运行阶段确定代理对象
  • 动态代理是在运行时根据Java代码指示动态生成的,相比较静态代理,优势在在于方便的对代理类的函数进行统一的处理,而不用修改每个代理类的方法。

Java中提供的动态代理方式有两种:JDK自带的动态代和CGLib实现代理。

JDK自带的代理方式

JDK自带的代理方式需要实现invocationHandler接口,实现invoke的方法。

  1. 提供一个接口类
    在这里插入图片描述
  2. 提供一个委托类
    在这里插入图片描述
  3. 实现动态代理,首先创建一个实现了InvocationHandler接口的辅助类。‘
public class UserProxy implements InvocationHandler {
    private Object object;
    public UserProxy(Object object){
        this.object = object;
    }
    /**
     * 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
     * @param proxy :就是动态代理生成的代理对象
     * @param method:就是调用的方法
     * @param args:表示该方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理类特有!");
        method.invoke(object,args);
        System.out.println("代理类特有!");
        return null;
    }
}
  1. 使用产生代理对象时,需要调用代理辅助类,调用委托方法。
public static void main(String[] args) {
        IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
        iUser.fun();
    }
  1. 结果:
    在这里插入图片描述

结果显示:当前的代理类调用fun方法,调用到代理辅助类UserProxy中的invoke方法,还调用到了委托类的fun实现。
JVM是如何自动实现invoke方法的调用呢?

CGLib实现代理

当代理没有接口类,此时Proxy和InvocationHandler机制不能使用了(JDK自带的代理模式的使用必须要有接口),此时可以使用CGLib库,采用底层字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截父类所有方法的调用,采用横切的逻辑。Spring AOP(横向切面)的技术技术就是使用动态代理。

引入依赖

      <!--CGLib-->
      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.2.2</version>
      </dependency>

父类

public class Test {
    public final void fun1(){//final修饰
        System.out.println("父类功能1");
    }
    public void fun2(){
        System.out.println("父类功能2");
    }
}

辅助类

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理类");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("代理类");
        return o1;
    }
}

测试类

public class MyCGLib {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置父类型
        enhancer.setSuperclass(Test.class);
        //创建增强对象
        enhancer.setCallback(new MyMethodInterceptor());
        Test test1 = (Test) enhancer.create();
        test1.fun1();
        System.out.println();
        test1.fun2();
    }
}

结果

在这里插入图片描述
只对非final方法进行了拦截。

Mybatis代理模式详解

Mybatis产生的代理是JDK自带的方式:仅在其中实现了接口。

mapper是如何添加进入的?
在这里插入图片描述
通过代码形式产生的会话工厂实例和读取配置形式是类似的。
代码形式中,Mapper接口是通过configuration.addMapper()形式来添加,参数为接口的class文件。

类似于:

Configuration configuration = new Configuration();
configuration.addMapper(StudentMapper1.class);//添加mapper

addMapper方法的实现

Configuration类中

  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
    //mapper实际上被添加到了MapperRegistry类中
  }

MapperRegistry类

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {//只添加接口
      if (hasMapper(type)) {//不允许接口重复添加
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //将接口存放到HashMap中
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

执行的Configuration.addMapper()操作,最终被放入到HashMap中,其名为knownMappers,knownMappers是MapperRegistry类的属性,是一个HashMap对象,key为当前class对象,value为MapperProxyFactory实例。

从getMapper入手分析

SQLSession调用getMapper()方法操作实际调DefaultSqlSession类的实现

DefaultSqlSession类

  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
    //直接调用configuration中的getMapper方法
  }

configuration类

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
    //直接调用mapperRegistry中的getMapper方法
  }

mapperRegistry类

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  	//class文件作为key获取到MapperProxyFactory类型的value值
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    //如果value值为空,抛出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
     //(重点)通过这个操作返回了代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

调用SQLSession.getMapper操作,最终会到上面的这个方法,根据接口在HashMap中找到对应的Value值(MapperProxyFactory的对象),然后通过调用该对象newInstance的方法,获取到代理对象。

MapperProxyFactory类

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
      //JDK自带的代理方式生成映射器代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

MapperProxy类

public class MapperProxy<T> implements InvocationHandler, Serializable {
 //当前类实现了InvocationHandler接口
 
  //实现invoke方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);//其中实现是调用委托类方法
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //使用缓存???
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行CRUD相关操作
    return mapperMethod.execute(sqlSession, args);
  }
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

总结

Mapper是如何注册的,Mapper接口是如何产生动态代理对象的?

答:Mapper接口在初始sqlSessionFactory的时候进行 Mapper接口注册,到了mapperRegistry类HashMap中,key是Mapper的class,value是当前的Mapper工厂。Mapper注册之后,可以从sqlSession中使用getMapper方法,使用了JDK自带的动态代理,产生目标接口Mapper的代理对象。
动态代理的代理辅助类是MapperProxy。

MyBatis缓存机制

缓存介绍

缓存提供了一种存储方式,当数据存储、访问数据时,可以直接访问缓存,减轻数据库的压力,提高数据库性能。
在这里插入图片描述
服务器和数据库是进行数据交互,无非是增删改查,查询操作比较多,查询操纵在数据库获取数据结果集,并不会引起数据本身的改变,增、删、改会改变数据库中的数据。
查询操作的特点:频率高,不会改变数据。
服务端到数据库是要进行网络交互。缓存可以减少服务端和数据库的交互,即减轻数据库压力,提高数据库的性能,缓存主要是用于查询缓存。
在这里插入图片描述
脏数据:
修改数据后查询到的数据和缓存中的旧数据一致。

为了避免脏数据问题,缓存更新时机如下:
查询操作:首先访问缓存,当缓存没有对应数据时,在数据库的中数据查询到后,插入到缓存中,后续的查询就可以直接访问缓存。
在变更操作时(修改,删除等)将数据变更到数据库时,直接将对应的缓存信息清空(hamp.remove(id))。
再次查询时,访问缓存,没有数据,就查询数据库将数据库返回的结果放入缓存。
通过该更新机制能够保证缓存数据是最新的数据。

缓存的实现方式:
1、在单实例服务器下,可以在当前服务代码中通过添加HashMap集合实例来充当缓存。
在这里插入图片描述
在集群服务中,本地缓存(HashMap)无法将缓存数据共享给其他的服务器。
2、在多服务器下,需要通过缓存共享的形式来缓存数据,使用缓存服务器处理。
缓存服务器的代表:Redis,memCache等

一级缓存

  • 一级缓存是SqlSession级别的缓存,在操作数据库是需要构造SqlSession会话对象。
  • 对同一个对象中的数据可以使用到缓存。
  • 不同的SQLSession之间的缓存不同享。

Mybatis是默认支持一级缓存,不需要做相关配置。

case1:同一个sqlSession下连续的执行同一个查询操作。

    @Test
    public void selectStudentById(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper1 mapper = sqlSession.getMapper(com.tl.dao.StudentMapper1.class);
        //第一次查询
        Student student1 = mapper.selectStudentById(1);
        //第二次查询
        Student student2 = mapper.selectStudentById(1);
        System.out.println(student1);
        System.out.println(student2);
    }

结果
在这里插入图片描述
在同一个SqlSession下,连续的查询同一个SQL语句,值查询一次数据库,第二次查询即访问缓存拿到结果。

case2:在同一个SqlSession下,先进行查询,在变更操作,然后进行同一个SQL查询。

 @Test
    public void selectStudentById(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper1 mapper = sqlSession.getMapper(com.tl.dao.StudentMapper1.class);
        //第一次查询
        Student student1 = mapper.selectStudentById(1);
        System.out.println(student1);
        //更新数据
        mapper.updateStudentAgeById(1);
        sqlSession.commit();
        //第二次查询
        Student student2 = mapper.selectStudentById(1);
        System.out.println(student2);
    }

结果
在这里插入图片描述
在第一次查询之后,会将存入数据缓存,进行了变更操作后,将缓存清空,第二次执行同一个SQL的查询操作时,缓存不存在了,就会继续查询数据库。

case3:不同的SqlSession下,同一个SQL查询操作。

@Test
    public void selectStudentById(){
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        StudentMapper1 mapper1 = sqlSession1.getMapper(com.tl.dao.StudentMapper1.class);
        //第一次查询
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper1 mapper2 = sqlSession2.getMapper(com.tl.dao.StudentMapper1.class);
        //第二次查询
        Student student2 = mapper2.selectStudentById(1);
        System.out.println(student2);
    }

结果
在这里插入图片描述
在同一个SqlSession实例下,连续查询操作可以使用缓存,在不同的SqlSession实例下,缓存不共享。

二级缓存

  • 二级缓存是Mapper级别的缓存,默认情况下二级缓存是关闭的。
  • 同一个Mapper下不同的SqlSession可以共享二级缓存,范围更大。
  • 不同Mapper缓存是相互隔离的。

使用步骤

注解形式
@CacheNamespace//开启二级缓存
public interface StudentMapper1 {
    @Select("select * from Student where id = #{id}")
    public Student selectStudentById(int id);
}
xml配置形式
  1. 需要在全局配置文件中打开二级缓存的开关。
    <settings>
        <!--开启二级缓存的开关-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
  1. 在mapper.xml文件中添加cache标签
    <cache></cache>
  1. 测试类
    @Test
    public void selectStudentById(){
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        StudentMapper1 mapper1 = sqlSession1.getMapper(com.tl.dao.StudentMapper1.class);

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper1 mapper2 = sqlSession2.getMapper(com.tl.dao.StudentMapper1.class);

        //第一次查询
        Student student1 = mapper1.selectStudentById(1);
        sqlSession1.commit();
        sqlSession1.close();
        System.out.println(student1);

        //第二次查询
        Student student2 = mapper2.selectStudentById(1);
        sqlSession2.commit();
        sqlSession2.close();
        System.out.println(student2);

    }
  1. 结果
    在这里插入图片描述
cache参数说明
    <!--
    cache参数说明:
    flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
    size:(引用次数)记录缓存对象的数量 默认值1024
    readOnly(只读)参数为true:false  表示缓存的数据对象实例是不能被修改
    eviction:缓存失效的策略  默认LRU
    LRU:最近最少使用的:移除最长时间不使用的
    FIFO:先进先出,按照缓存对象的顺序来淘汰
    SOFT:软引用
    WEAK:弱引用
    -->
    <cache eviction="LRU" flushInterval="10000" size="12"></cache>

高级映射

在多表联合查询操作时,存在一对一、一对多和多对多的关系。

以订单数据模型为例

在这里插入图片描述
数据库表:
user:用户表:记录购买商品的用户信息
Orders:订单表,记录用户创建的所有订单
orderdetails:记录订单的详细的购买信息
items:商品表:记录商品信息

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `sex` char(1) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '下单用户id',
  `number` varchar(32) NOT NULL COMMENT '订单号',
  `createtime` datetime NOT NULL COMMENT '创建订单时间',
  `note` varchar(100) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `FK_orders_1` (`user_id`),
  CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8

//订单明细表
CREATE TABLE `orderdetail` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orders_id` int(11) NOT NULL COMMENT '订单id',
  `items_id` int(11) NOT NULL COMMENT '商品id',
  `items_num` int(11) DEFAULT NULL COMMENT '商品购买数量',
  PRIMARY KEY (`id`),
  KEY `FK_orderdetail_1` (`orders_id`),
  KEY `FK_orderdetail_2` (`items_id`),
  CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8

CREATE TABLE `items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '商品名称',
  `price` float(10,1) NOT NULL COMMENT '商品定价',
  `detail` text COMMENT '商品描述',
  `pic` varchar(64) DEFAULT NULL COMMENT '商品图片',
  `createtime` datetime NOT NULL COMMENT '生产日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

测试数据

insert  into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`sex`,`address`) values (1,'王五','2',NULL),(10,'张三','1','北京市'),(16,'张小明','1','陕西西安'),(22,'陈小明','1','陕西西安'),(24,'张三丰','1','陕西西安'),(25,'陈小明','1','陕西西安'),(26,'王五',NULL,NULL);

/*Data for the table `orders` */

insert  into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);

/*Data for the table `orderdetail` */

insert  into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);

数据库模型分析
在这里插入图片描述

一对一映射

需求:通过订单号查询订单及用户信息。
分析:订单号是在订单表中,通过订单表中的user_id字段可以查询到用户信息。
主表:orders 从表:user
SQL:select * from orders o,user u where o.user_id = u.id and o.number=?

在Orders类中添加user实例

private int id;
    private int userId;
    private  long number;
    private Date createtime;
    private String note;

    private User user;
    //省略getter和setter方法

接口

public Orders getOrdersByNumber(String number);

mapper.xml配置文件

    <select id="getOrdersByNumber" resultMap="OrderUserResultMap">
        select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address
        from orders o,user u where o.user_id = u.id and o.number = #{number}
    </select>

方法一:使用resultMap一一映射

<resultMap id="OrderUserResultMap" type="com.tl.pojo.Orders">
        <result column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>

        <result column="u_id" property="user.id"/>
        <result column="u_username" property="user.username"/>
        <result column="u_sex" property="user.sex"/>
        <result column="u_address" property="user.address"/>
    </resultMap>

方法二:使用resultMap提供的association配置一一对应

<resultMap id="OrderUserResultMap" type="com.tl.pojo.Orders">
        <result column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>

        <!--
        association:用于映射关联查询单个对象的信息
        property:关联查询对应到自定义对象的属性
        javaType:映射的java属性的全限定名
        -->
        <association property="user" columnPrefix="u_" javaType="user">
            <result column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </association>

    </resultMap>

方法三:使用extends属性继承当前resultMap

    <resultMap id="OrderResultMap" type="orders">
        <result column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
    </resultMap>
    <resultMap id="OrderUserResultMap" extends="OrderResultMap" type="orders">
        <association property="user" columnPrefix="u_" javaType="user">
            <result column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

方法四:使用association中的resultMap的属性(推荐)

	<resultMap id="OrderResultMap" type="orders">
        <result column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
    </resultMap>
   
    <resultMap id="UserResultMap" type="user">
        <result column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
    </resultMap>

    <resultMap id="OrderUserResultMap" extends="OrderResultMap" type="orders">
        <association property="user" columnPrefix="u_" resultMap="UserResultMap"/>
    </resultMap>

结果
在这里插入图片描述

一对多映射

需要关联映射是一个list结果。
需求:通过用户ID查询用户的订单信息。
分析:通过用户(主表:user)的关键信息查询所有相关的订单(从表:orders)。
SQL:select u.,o. from user u,orders o where u.id=o.user_id and u.id = ?

在User中添加一个Orders类型的集合

	private int id;
    private String username;
    private String sex;
    private String address;

    private List<Orders> orders;

接口

public User getUserOrderById(int id);

mapper.xml配置文件

SQL片段的复用

    <sql id = "UserProperty">
        u.id,
        u.username,
        u.sex,
        u.address
    </sql>
    <sql id="OrdersProperty">
        o.id o_id,
        o.user_id o_user_id,
        o.number o_number,
        o.createtime o_createtime,
        o.note o_note
    </sql>
    
    <select id="getUserOrderById" resultMap="UserOrdersResultMap">
        select <include refid="UserProperty"/>,
        <include refid="OrdersProperty"/> from user u,orders o
        where o.user_id = u.id and o.user_id = #{id}
    </select>
    <resultMap id="UserResultMap" type="user">
        <result column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
    </resultMap>
        <!--
	    使用resultMap中的Collection配置一对多关系
	    Collection:将关联查询的多条记录映射到集合兑现中
	    property:将关联查询的多条记录映射到的属性名:orders10s
	    ofType:执行映射的集合属性中的自定义类型
	    extends:继承主类性
	   -->	
    <resultMap id="UserOrdersResultMap" extends="UserResultMap" type="user">
        <collection property="orders" columnPrefix="o_" ofType="orders">
            <result column="id" property="id"/>
            <result column="user_id" property="userId"/>
            <result column="number" property="number"/>
            <result column="createtime" property="createtime"/>
            <result column="note" property="note"/>
        </collection>
    </resultMap>

结果
在这里插入图片描述

ResultMap和ResultType的总结

resultType:
作用:将查询结果的SQL列名和pojo类的属性名完成一致性的映射。
缺点:若SQL结果列名和pojo类属性名不一致,则无法自动完成映射。

resultMap:
使用association和Collection完成一对一和一对多高级映射(对结果有特殊要求的映射对象)

  • association:
    作用:将关联查询的信息映射到一个pojo对象中
    使用resultType无法将查询结果映射到pojo对象的pojo属性中时,选择resultMap来处理(association)
  • Collection:
    将关联查询映射到一个List集合中
    使用resultType无法将查询的结果映射到Pojo对象的List类型的pojo属性中时,使用resultMap的Collection标签来处理。

延时加载

懒加载:先从单表查询,需要时再从关联表去查询数据,这样能大大提高数据库的性能,因为单表查询要比多表查询快。association和Collection具备延时加载功能。
使用懒加载在全局配置XML文件的setting配置
在这里插入图片描述

    <settings>
        <!--懒加载配置-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <!--先通过number在orders表中查询到user_id,再用这个user_id去user表中查询-->
    <resultMap id="OrderUserResultMap1" extends="OrderResultMap" type="orders">
        <association property="user" column="id={user_id}" select="com.tl.dao.OrdersMapper.getUserById"/>
                                               <!--接口参数      select属性:关联查询的已存在方法的全路径-->
    </resultMap>
    <select id="getOrderByNumber" resultMap="OrderUserResultMap1">
        select * from orders where number = #{number}
    </select>
    <select id="getUserById" resultType="user">
        select * from User where id = #{id}
    </select>

结果
在这里插入图片描述

逆向工程

Mybatis-Generator是Mybatis和ibatis的代码生成器,可以生成简单CRUD操作的xml配置文件,Mapper的接口文件(Mapper接口)、实体类(POJO),可以有效的减少开发者的工作量,减少写手动写SQL的过程,减少出错风险。
思想:需要先将数据库中表生成,包含字段,字段属性 可以来映射生成pojo类以及基本的Mapper的XML文件和Mapper接口文件。

Generator的官网:http://mybatis.org/generator/

Generator的使用步骤

引入依赖

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

mybatis-generator.xml文件

<!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:必填,上下文的ID,用于在生成错误时提示
    targetRuntime:
       Mybatis3:用于生成mybatis 3.0以上版本的内容,包含XXXBySample
       Mybatis3Simple:类似于mybatis3,但是不生成XXXBySample
    -->
    <context id="simple" targetRuntime="MyBatis3Simple">
        <!--
        数据库连接的信息:驱动类,连接的地址,账号,密码
        -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                         connectionURL="jdbc:mysql://localhost:3306/product"
                         userId="root"
                         password="123456"
        />

        <!--
        生成pojo类信息
        targetPackage:生成pojo类的包路径
        targetProject:生成pojo类的位置
        -->
        <javaModelGenerator targetPackage="com.tl.pojo" targetProject="src/main/java">
            <!--
            enableSubPackages:是否生成schema作为包的后缀
            -->
            <property name="enableSubPackages" value="false" />
            <!--
            trimStrings:从数据库返回的值清理前后的空格
            -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--
        生成Mapper.xml文件的配置信息
        -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>

        <!--
        生成Mapper的接口文件的配置
        -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.tl.dao" targetProject="src/main/java"/>

        <!--指定数据库的表-->
        <table tableName="orderdetail" />
    </context>
</generatorConfiguration>

GeneratorDemo类

public class GeneratorDemo {
    public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        //读取配置文件路径
//        File configFile = Resources.getResourceAsFile("mybatis-generator.xml");
        File configFile = new File("F:\\java代码\\MyBatisTest\\src\\main\\resources/mybatis-generator.xml");
        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);
    }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值