关于MyBatis框架这一篇就够了

一、MyBatis框架简介

1. 什么是框架?

框架(Framework)是整个或者部分系统的可重用设计,从应用角度看框架可以被当为一组抽象构件及构件实例间交互的方法;在应用目的的角度来看框架是一种可以被应用开发者定制的应用骨架。总结来讲,框架是一种半成品应用,是一种组件,可以辅助你开发自己的系统。

框架解决的问题

框架要解决的最重要的一个问题是技术整合的问题,在J2EE的框架中,有着各种各样的技术,不同的软件企业需要从J2EE中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层,框架实现了部分功能,并能很好的进行业务逻辑和功能的整合。

软件开发中的分层思想

为了方便对软件进行开发和管理,实现软件工程中的“高内聚,低耦合”,将开发中的问题划分开各个解决,使得应用易于控制,易于扩展,易于资源管理,常见分层思想-MVC设计思想。通过分层更好的实现了各个部分的职责,再细分化出不同的框架,分别解决各层关注的问题。

分层开发中的常见框架

① 解决数据的持久层问题的框架–MyBatis

② 解决web层问题的框架–springMVC

③ 解决技术整合问题得框架–spring

2. MyBatis框架简介

MyBatis是一个优秀的基于Java的持久层框架,内部封装了jdbc,开发者仅需关注sql语句本身,不需要花更多的精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

MyBatis通过注解或xml配置的方式将需要执行的statement配置起来,并通过Java对象和statement中sql的动态参数进行映射生成最终需要执行的sql语句,最后由Mybatis框架执行sql语句并将结果映射为Java对象并返回。

Mybatis采用 ORM思想决了实体类和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc底层API的访问细节,让我们不用和jdbc API打交道,也能完成数据库的访问操作。

强烈推荐阅读:MyBatis官方中文文档

二、MyBatis实现基本CRUD操作

1. 基本配置流程:

① 创建Maven工程,并添加坐标导入jar包

② 书写与数据库表映射的实体类

③ 书写持久层接口

④ 书写持久层接口的映射文件或在持久层接口书写注解

⑤ 书写配置文件

2. 持久层映射配置文件的使用

2.1 映射配置文件中的标签

2.1.1 常见的标签

:用于配置返回的数据集,建立查询结果列名和实体类属性名之间的对应的关系。

id属性:指定名称

type属性:指定实体类名

  • 内部标签 :用于配置与数据库表中的关键字对应的字段。
  • 内部标签 :用于配置与数据库中的非关键字段对应的字段。
    • property属性:用于指定实体类的属性
    • colum属性:用于指定数据库列的属性

:用于书写通用的SQL语句,可以搭配 标签对通用SQL语句进行抽取。

2.1.2 与查询相关的标签

:用于书写数据库插入相关的操作

:用于书写数据库删除相关的操作

:用于书写数据库更新相关的操作

标签

@Result :实现结果集的封装,相当于 标签

  • id属性:是否为主键字段
  • column属性:与数据库对应的列名
  • property属性:需要配置的属性名
  • one属性:需要使用 @ 注解 (@Result (one = @One) ())
  • many属性:需要使用 @Many 注解 (@Result (many = @Many) ())

@Results :可以配合 @Result 一起使用,封装多个结果集。相当于 标签

@ResultMap :实现引用 @Results 定义的封装

@One :实现一对一结果集封装,相当于 标签,在注解中用于指定子查询返回单一对象。

  • select属性:指定用于多表查询的sqlmapper
  • fetchType属性:会覆盖全局的配置参数 lazyLoadingEnabled
  • 使用格式: @Result(column = "", property = "", one = @One(select = ""))

@Many :实现一对多结果集封装,相当于 标签,在注解中用于指定子查询返回对象集合。

  • 聚焦元素用来处理一对多的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但注解中可以不定义。
  • 使用格式:@Result(property = "", column = "", many = @Many(select = ""))

@SelectProvider :实现动态SQL映射

@CacheNameSpace :实现注解二级缓存的应用

2. 注解实现基本CRUD

使用步骤:

  • 书写和数据库对应的实体类
  • 书写数据持久层接口
  • 书写配置文件

代码示例:

持久层接口IUserDao

public interface IUserDao {
     

    /**
     * 增加新的用户
     * @param user
     */
    @Insert("insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})")
    void saveUser(User user);

    /**
     * 根据id删除用户
     * @param userId
     */
    @Delete("delete from user where id = #{userId}")
    void deleteUserById(Integer userId);

    /**
     * 修改用户信息
     * @param user
     */
    @Update("update user set username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address} where" +
            " id = #{id}")
    void updateUser(User user);

    /**
     * 获取全部用户
     * @return
     */
    @Select("select * from user")
    List<User> getUsers();

    /**
     * 根据id获取用户
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{userId}")
    User getUserById(Integer userId);

    /**
     * 根据名称模糊查询
     * @param userName
     * @return
     */
    @Select("select * from user where username like '%${userName}%'")
    List<User> getUserByName(String userName);

    /**
     * 查询用户总数
     * @return
     */
    @Select("select count(*) from user")
    int getTotalUser();
}

此处仅展示注解相关代码,具体详细代码请查看:Mybatis注解CRUD

3. 注解进阶操作

使用注解实现一对多的关联查询和缓存懒加载

书写步骤:

  • 书写和数据库对应的实体类
  • 书写持久层接口
  • 书写配置文件

代码示例:

持久层接口IUserDao,配置一对多关系

public interface IUserDao {
     

    /**
     * 查询所有用户
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value={
     
        @Result(id = true, property = "userId",column = "id"),
        @Result(property = "userName", column = "username"),
        @Result(property = "userBirthday", column = "birthday"),
        @Result(property = "userSex", column = "sex"),
        @Result(property = "userAddress", column = "address"),
        @Result(property = "accounts", column = "id",
                many = @Many(select = "cn.bruce.dao.IAccountDao.getAccountByUid", fetchType = FetchType.LAZY))
    })
    List<User> getUsers();

    /**
     * 根据id获取用户
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{userId}")
    @ResultMap("userMap")
    User getUserById(Integer userId);

    /**
     * 根据名称进行模糊查询
     * @param userName
     * @return
     */
    @Select("select * from user where username like '%${userName}%'")
    @ResultMap("userMap")
    List<User> getUserByName(String userName);

}

持久层接口IAccountDao,配置一对一关系

public interface IAccountDao {
     

    /**
     * 获取全部账户
     * @return
     */
    @Select("select * from account")
    @Results(id = "accountMap", value = {
     
        @Result(id = true, property = "id", column = "id"),
        @Result(property = "uid", column = "uid"),
        @Result(property = "money", column = "money"),
        @Result(property = "user", column = "uid",
                one = @One(select = "cn.bruce.dao.IUserDao.getUserById", fetchType = FetchType.EAGER))
    })
    List<Account> getAccounts();

    /**
     * 根据uid获取账户信息
     * @param userId
     * @return
     */
    @Select("select * from account where uid = #{userId}")
    List<Account> getAccountByUid(Integer userId);
}

此处仅展示持久层的注解配置,具体的代码请查看:Mybatis注解配置一对多

九、手动实现Mybatis框架基本结构

经过上面的介绍我们对MyBatis框架有了基本的认识和了解,我们也可以尝试实现MyBatis框架的部分功能,比如查询相关的功能。我们手动实现MyBatis框架不是为了取代已经设计好的框架,我们毕竟了解的还不够深入,对于其中的诸多设计细节理解也不够深入。我们手动实现的目的在于,了解框架解析执行的原理和涉及到的设计模式,加强对MyBatis框架的理解,帮助我们更好的使用框架。

1. MyBatis框架执行的基本流程:

关于MyBatis框架这一篇就够了_第7张图片

2. 设计的基本思路

  • 设计sqlSession相关的类

    • 设计创建工厂的类
    • 设计代理工厂类
    • 设计真正创建Dao对象的类
  • 设计配置Bean

    • 设计数据库连接相关的Bean
    • 设计执行SQL语句和结果类型的Bean
  • 设计文件IO读取的类–用于进行XML配置

  • 设计自定义注解类–用于进行注解配置

  • 设计工具类

    • 设计解析xml的工具类获取SQL语句
    • 设计解析注解的工具类获取SQL语句

具体的实现代码比较多,此处就不再展示,详细代码请查看:手动实现MyBatis框架基本结构

3. 应用到的设计模式

3.1 工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式调用UML图

关于MyBatis框架这一篇就够了_第8张图片

实现代码:

// 图形接口,没有指明具体的类型
public interface Shape {
     
   void draw();
}

// 矩形类,实现图形接口
public class Rectangle implements Shape {
     

   @Override
   public void draw() {
     
      System.out.println("Inside Rectangle::draw() method.");
   }
}

// 正方形类,实现图形接口
public class Square implements Shape {
     

   @Override
   public void draw() {
     
      System.out.println("Inside Square::draw() method.");
   }
}

// 圆形类,实现图形接口
public class Circle implements Shape {
     

   @Override
   public void draw() {
     
      System.out.println("Inside Circle::draw() method.");
   }
}

// 图形工厂,用于创建图形对象
public class ShapeFactory {
     

   //使用 getShape 方法获取形状类型的对象
   public Shape getShape(String shapeType){
     
      if(shapeType == null){
     
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
     
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
     
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
     
         return new Square();
      }
      return null;
   }
}

// 测试类,测试工厂模式
public class FactoryPatternDemo {
     

   public static void main(String[] args) {
     
      ShapeFactory shapeFactory = new ShapeFactory();

      //获取 Circle 的对象,并调用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("CIRCLE");

      //调用 Circle 的 draw 方法
      shape1.draw();

      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");

      //调用 Rectangle 的 draw 方法
      shape2.draw();

      //获取 Square 的对象,并调用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");

      //调用 Square 的 draw 方法
      shape3.draw();
   }
}

3.2 代理模式

使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

3.1.1 静态代理

代码示例:

耐克公司与合作工厂共同生产衣服,耐克公司不需要关注衣服具体是怎样生产的,只需要提供设计图即可,合作工厂会帮助耐克完成生产,与耐克合作的工厂就是代理类。

interface ClothFactory{
     
    void produceCloth();
}

//被代理类
class NikeClothFactory implements ClothFactory{
     

    @Override
    public void produceCloth() {
     
        System.out.println("Nike 生产衣服");
    }
}

//代理类
class ProxyClothFactory implements ClothFactory{
     

    private ClothFactory factory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory) {
     
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
     
        System.out.println("代理工厂做一些准备工作");

        factory.produceCloth();

        System.out.println("代理工厂做一些后续的收尾工作");

    }
}

//测试
public class StaticProxyTest {
     
    public static void main(String[] args) {
     

        //创建被代理类的对象
        ClothFactory nike = new NikeClothFactory();

        //创建代理类的对象
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);

        proxyClothFactory.produceCloth();
    }
}

静态代理的缺点:

① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。

② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

3.1.2 动态代理

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

相比于静态代理的优点:

抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

动态代理的实现:

  1. 创建一个实现接口 InvocationHandler 的类,它必须实现invoke方法,以完成代理的具体操作。
  2. 创建被代理类以及接口
  3. 通过Proxy的静态方法 newProxyInstance(ClassLoader loader, Class...interface, InvocationHandler h) 创建一个接口代理
  4. 通过代理类的实例调用被代理类的方法

代码实现:

interface Human {
     
    String getBelief();

    void eat(String food);
}

//被代理类
class SuperMan implements Human {
     

    @Override
    public String getBelief() {
     
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
     
        System.out.println("I like eat " + food);
    }
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */

//创建继承了InvocationHandler接口的类
class MyInvocationHanlder implements InvocationHandler {
     
    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj) {
     
        this.obj = obj;
    }
    //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     

        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);

        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;
    }
}

class ProxyFactory {
     
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj) {
     
        MyInvocationHanlder hanlder = new MyInvocationHanlder();
        hanlder.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),hanlder);

    }
}

//测试动态代理
public class ProxyTest {
     
    public static void main(String[] args) {
     
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("火锅");
    }
}

我是大仙如果觉得本文对你有帮助,可以转发关注支持一下

你可能感兴趣的:(Java,java,mysql,mybatis)