SSM框架学习笔记——Mybatis

Spring

IoC(Inversion of Control 控制反转):

IoC是一个容器,它会认为一切Java资源都是Java Bean,容器的目标是管理这些Bean和它们之间的关系。
所有IoC中装载了各种Bean, 也可理解为Java的各种资源,包括Java Bean的创建、事件、行为等,都由IoC管理。
Java Bean间可能存在依赖关系,IoC也能够进行管理。

Example:
用户可以使用两种插座(这两种插座都实现了同一个接口Socket)
通过代码实现使用Socket1

Socket socket = new Socket1();
user.setSocket(socket);
user.useSocket();

使用Socket socket = new Socket1();,后Socket就和Socket1捆绑在一起了。
这样会有一个弊端,如果需要使用其他插座,就需要修改代码了。
这种情况Socket接口和其实现类Socket1耦合了。

Socket socket = new Socket2();
user.setSocket(socket);
user.useSocket();

Spring IoC可以解决这个问题,我们不使用new的方式创建对象,而是让Spring IoC容器自己通过配置找到插座。



         

如果要使用Socket2,只需要把配置改为:


你不需要去找资源,只要向IoC容器描述所需资源,Spring IoC自己会找到你所需要的资源。
IoC根据描述找到使用者需要的资源,控制权在Spring IoC容器中,这就是控制反转的含义。

AOP

AOP常用于数据库事务的编程,在完成第一步数据库数据更新后,不知道下一步是否会成功,如果下一步失败,会使用数据库事务的回滚功能去回滚事务,使得下一步的数据库更新也作废。
在AOP实现的数据库事务管理中,是以异常作为消息的。在默认状态下,只要Spring收到了异常消息,它就会将数据库的事务回滚,从而保证数据的一致性。
我们只需要关注业务代码,知道只要发生了异常,Spring会回滚事务就行了。

MyBatis

MyBatis的优势在于灵活,它几乎可以代替JDBC ,同时提供了接口编程。目前MyBatis的数据访问层DAO ( Data Access Objects)是不需要实现类的,它只需要一个接口和XML(或者注解)。

我们把POJO 对象和数据库表相互映射的框架称为对象关系映射( Object Relational Mapping)框架。无论MyBatis或者Hibernate 都可以称为ORM 框架,只是Hibernate 的设计理念是完全面向POJO 的,而MyBatis 则不是。
Hibernate基本不再需要编写SQL 就可以通过映射关系来操作数据库,是一种全表映射的体现:而MyBatis 则不同,它需要我们提供SQL 去运行。

Hibernate

SSM框架学习笔记——Mybatis_第1张图片
SSM框架学习笔记——Mybatis_第2张图片
Hibernate对POJO和表进行了全表映射。然后对POJO操作,从而对t_role表里的数据进行增删改查。代码里没有SQL,因为Hibernate会根据映射关系来生成对应的SQL,只需要操作POJO就能操作对应数据库中的表了。

MyBatis

与Hibernate不同,MyBatis不屏蔽SQL,程序员可以自己制定SQL规则,这样能更加精确地定义SQL。
SSM框架学习笔记——Mybatis_第3张图片
resultMap元素用于定义映射规则,Mybatis在满足一定规则下,完成自动映射,增删改查对应四个元素。

resultMap中type为返回类型,可以为JavaBean对象或者是map对象。

< id/>和< result/>是resultMap中最基本的映射内容,它们都可以将查询结果中一个单独列的值映射为返回结果对象中的简单数据类型(字符串,整型,双精度浮点数,日期等)的单独属性或字段。这两者之间的唯一不同是id 表示的结果将是当比较对象实例时用到的标识属性。

mapper元素中的namespace属性,需要和一个接口的全限定类名一致,而里面SQL的id也需要和接口定义的方法完全保持一致。
映射文件(接口):
SSM框架学习笔记——Mybatis_第4张图片
SSM框架学习笔记——Mybatis_第5张图片

Java反射技术

通过反射来构建对象(不带参数):

public ReflectServiceImpl getInstance() {
	ReflectServiceImpl object = null;
	try {
		object = (ReflectServiceImpl) Class.forName("com.lean.ssm.chapter2.reflect.ReflectServiceImpl")
				.newInstance();
	} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
		ex.printStackTrace();
	}
	return object;
}   

一个类的所有构造方法都至少有一个参数的情况:

public ReflectServiceImpl2 getInstance() {
	ReflectServiceImpl2 object = null;
	try {
		object = (ReflectServiceImpl2) Class.forName("com.lean.ssm.chapter2.reflect.ReflectServiceImpl2").
				getConstructor(String.class).newInstance("张三");
	} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
		ex.printStackTrace();
	}
	return object;
}   

反射的优点是只要配置就可以生成对象, 可以解除程序的稿合度,比较灵活。反射的缺点是运行比较慢。但是大部分情况下为了灵活度, 降低程序的稿合度,我们还是会使用反射的,比如Spring IoC 容器。

反射方法:

public Object reflectMethod() {
	Object returnObj = null;
	ReflectServiceImpl target = new ReflectServiceImpl();
	try {
		Method method = ReflectServiceImpl.class.getMethod("sayHello", String.class);
		returnObj = method.invoke(target, "张三");
	} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
			| InvocationTargetException ex) {
		ex.printStackTrace();
	}
	return returnObj;
}

反射生成对象和反射调度方法

public Object reflect() {
	ReflectServiceImpl object = null;
	try {
		object = (ReflectServiceImpl) Class.forName("com.lean.ssm.chapter2.reflect.ReflectServiceImpl")
				.newInstance();
		Method method = object.getClass().getMethod("sayHello", String.class);
		method.invoke(object, "张三");
	} catch (NoSuchMethodException | SecurityException | ClassNotFoundException | IllegalAccessException
			| IllegalArgumentException | InvocationTargetException | InstantiationException ex) {
		ex.printStackTrace();
	}
	return object;
}

动态代理和责任链模式

动态代理的意义在于生成一个占位(又称代理对象) , 来代理真实对象, 从而控制真实对象的访问。

代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象。

我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理必须分为两个步骤:

  1. 代理对象和真实对象建立代理关系。
  2. 实现代理对象的代理逻辑方法。

JDK动态代理

JDK动态代理必须借助接口才能产生代理对象,所以定义一下接口:

public interface HelloWorld {
    public void sayHelloWorld();
}

并提供实现类:

public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World");
    }
}

动态代理绑定和代理逻辑实现:

/**
 * 在JDK 动态代理中,要实现代理逻辑类必须去实现java.lang.reflect.InvocationHandler
 * 接口,它里面定义了一个invoke 方法,并提供接口数组用于下挂代理对象
 */
public class JDKProxy implements InvocationHandler {

    //真实对象
    private Object target = null;

    /**
     * 建立代理对象和真实对象的关系,并返回代理对象
     * @param target
     * @return 代理对象
     */
    public Object bind(Object target){
        //用类属性保存真实对象
        this.target = target;
        //建立并生成代理对象
        //三个参数
        //1.真实对象的类加载器
        //2.把生成的动态代理对象挂在哪些接口下 -> 真实对象实现的接口
        //3.定义实现方法逻辑的代理类 -> this表示当前对象 它实现的invoke方法就是代理逻辑方法的实现
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象 即bind方法生成的对象
     * @param method 当前调度方法
     * @param args 当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入代理逻辑方法");
        System.out.println("在调度真实对象之前的服务");
        //相当于调度真实对象的方法,只是通过反射实现
        Object obj = method.invoke(target, args);
        System.out.println("在调度真实对象之后的服务");
        return obj;
    }


}

测试代码:

public class Test {

    public static void main(String[] args) {
        JDKProxy jdk = new JDKProxy();
        //绑定关系,因为挂在HelloWorld接口下,所以声明HelloWorld proxy
        //调用bind方法,参数传入真实对象HelloWorldImpl
        //此时proxy对象已经是一个代理对象,在调用方法时,它会进入代理方法逻辑invoke里
        HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
        //代理对象调用sayHelloWorld方法时进入了代理的逻辑
        proxy.sayHelloWorld();

        /*  输出结果:
        *
            进入代理逻辑方法
            在调度真实对象之前的服务
            Hello World
            在调度真实对象之后的服务
        *
        * */
    }
}

拦截器

由于动态代理一般都比较难理解,程序设计者会设计一个拦截器接口供开发者使用,开发者只要知道拦截器接口的方法、含义和作用即可,无须知道动态代理是怎么实现的。

用JDK动态代理来实现一个拦截器的逻辑,为此先定义拦截器接口Interceptor。

/**
 * proxy:代理对象
 * target:真实对象
 * method:方法
 * args:方法参数
 *
 * before在真实对象前调用,当返回true,则反射真实对象的方法;当返回false,则调用around方法。
 * 在反射真实对象方法或调用around方法后,调用after方法。
 */
public interface Interceptor {

    public boolean before(Object proxy, Object target, Method method, Object[] args);

    public void around(Object proxy, Object target, Method method, Object[] args);

    public void after(Object proxy, Object target, Method method, Object[] args);
}

实现接口:

public class MyInterceptor implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法前逻辑");
        return false;//不反射被代理对象原有方法,调用around
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代了被代理对象的方法,即真实对象的方法");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法后逻辑");
    }
}

在JDK动态代理中使用拦截器:

public class InterceptorJdkProxy implements InvocationHandler {

    private Object target; //真实对象
    private String interceptorClass = null; //拦截器全限定名

    public InterceptorJdkProxy(Object target,String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }


    /**
     *
     * @param target 真实对象
     * @param interceptorClass 拦截器全限定类名
     * @return 代理对象【占位】
     */
    public static Object bind(Object target, String interceptorClass){
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InterceptorJdkProxy(target,interceptorClass));
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass == null){
            //没有设置拦截器则直接反射原有方法
            return method.invoke(target,args);
        }
        Object result = null;
        //通过反射生成拦截器
        Interceptor interceptor = (Interceptor)
                Class.forName(interceptorClass).newInstance();
        //调用前置方法
        if(interceptor.before(proxy,target,method,args)){
            //反射原有对象方法
            result = method.invoke(target,args);
        }else {
            //返回false执行around方法
            interceptor.around(proxy,target,method,args);
        }
        //调用后置方法
        interceptor.after(proxy,target,method,args);
        return result;
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),
                "chapter2.intercept.MyInterceptor");
        proxy.sayHelloWorld();
    }
}
/*  执行结果:
    反射方法前逻辑
    取代了被代理对象的方法,即真实对象的方法
    反射方法后逻辑*/

责任链模式

当一个对象在一条链上被多个拦截器拦截处理(拦截器也可以选择不拦截处理它)时,我们把这样的设计模式称为责任链模式,它用于一个对象在多个角色中传递的场景。

定义三个拦截器

public class Interceptor1 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器1的before");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器1的after");
    }
}
public class Interceptor2 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器2的before");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器2的after");
    }
}

public class Interceptor3 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器3的before");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("拦截器3的after");
    }
}

测试加入责任链逻辑拦截器的代理对象

    public static void main(String[] args) {

        HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"chapter2.intercept.Interceptor1");
        HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1,"chapter2.intercept.Interceptor2");
        HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2,"chapter2.intercept.Interceptor3");

        proxy3.sayHelloWorld();

/*      拦截器3的before
        拦截器2的before
        拦截器1的before
        Hello World
        拦截器1的after
        拦截器2的after
        拦截器3的after*/

    }

观察者模式

观察者模式又称为发布订阅模式,是对象的行为模式。观察者模式定义了一种一对多
的依赖关系,让多个观察者对象同时监视着被观察者的状态,当被观察者的状态发生变化
时, 会通知所有观察者, 并让其自动更新自己。

public class ProductList extends Observable {

    private List productList = null; //产品列表

    private static ProductList instance; //类唯一实例

    private ProductList(){} //构造方法私有化
    //构建方法私有化,避免通过n ew 的方式创建对象, 而是通过getInstance方法获得产
品列表单例,这里使用的是单例模式。

    /**
     * 取得唯一实例
     * @return
     */
    public static ProductList getInstance(){
        if(instance == null){
            instance = new ProductList();
            instance.productList = new ArrayList();
        }

        return instance;
    }

    /**
     * 增加观察者(电商接口)
     * @param observer
     */
    public void addProductObserver(Observer observer){
        this.addObserver(observer);
    }


    /**
     * 核心逻辑在addProduct 方法上。在产品列表上增加了一个新的产品,然后调用
     * setChanged 方法。这个方法用于告知观察者当前被观察者发生了变化,如果没有,
     * 则无法触发其行为。最后通过notifyObservers 告知观察者,让它们发生相应的动作,
     * 并将新产品作为参数传递给观察者。
     * @param newProduct
     */
    public void addProduct(String newProduct){
        productList.add(newProduct);
        System.out.println("产品列表新增产品:"+newProduct);
        this.setChanged();//设置被观察对象发生变化
        this.notifyObservers(newProduct);//通知观察者,并传递新产品
    }

}
public class TaoBaoOb implements Observer {
    @Override
    public void update(Observable o, Object product) {
        String newProdcut = (String) product;
        System.out.println("发送新产品:"+newProdcut+"到淘宝");
    }
}
public class JingDongOb implements Observer {
    @Override
    public void update(Observable o, Object product) {
        String newProdcut = (String) product;
        System.out.println("发送新产品:"+newProdcut+"到京东");
    }
}

测试代码:

   public static void main(String[] args) {
        ProductList observable = ProductList.getInstance();
        TaoBaoOb tb = new TaoBaoOb();
        JingDongOb jd = new JingDongOb();
        observable.addObserver(tb);
        observable.addObserver(jd);
        observable.addProduct("新产品XXX");


        /*输出:
        * 产品列表新增产品:新产品XXX
          发送新产品:新产品XXX到京东
          发送新产品:新产品XXX到淘宝*/
    }

工厂模式和抽象工厂模式

在大部分的情况下,我们都是以new 关键字来创建对象的。举个例子,现实中车子的
种类可能很多,有大巴车、轿车、救护车、越野车、卡车等,每个种类下面还有具体的型
号,一个工广生产如此多的车会难以管理,所以往往还需要进一步拆分为各个分工厂: 大
巴车、轿车等分工厂。但是客户不需要知道工厂是如何拆分的,他只会告诉客服需要什么
车,客服会根据客户的需要找到对应的工厂去生产车。对客户而言,车厂只是一个抽象概
念,他只是大概知道有这样的一个工厂能满足他的需要。

普通工厂 Simple Factory

例如,有个Product的产品接口,它下面有5 个实现类
Product1、Product2 、Pro duct3 、Product4 、Products 。它们属于一个大类,可以通过一个工厂去管理它们的生成,但是由于类型不同,所以初始化有所不同。为了方便使用产品工厂
( ProductFactory )类来创建这些产品的对象,用户可以通过产品号来确定需要哪种产品。

伪代码:

public class ProductFactory{
public static Product createProduct(String productNo) {
		switch (productNo) {
			case ” 1 ”: return new Productl(xxxx);
			case ” 2 ”: return new Product2(xxxx);
			case ” 3 ”: return new Product3(xxxx);
			case ” 4 ”: return new Product4(xxxx);
			case ” 5 ”: return new Product5(xxxx);
			default : throw new
				NotSupportedException ( ” 未支持此编号产品生产。”);
				}
}
}				

对于程序调用者而言,它只需要知道通过工厂的createProduct 方法,指定产品编号——productNo 可以得到对应的产品,而产品满足接口Product 的规范,所以初始化就简单了许
多。对于产品对象的创建,可以把一些特有产品规则写入工厂类中。

抽象工厂

对于普通工厂而言,它解决了一类对象创建问题,但是有时候对象很复杂,有几十种,
又分为几个类别。如果只有一个工厂,面对如此多的产品,这个工厂需要实现的逻辑就太
复杂了,所以我们希望把工厂分成好几个,这样便于工厂产品规则的维护。

但是设计者并不想让调用者知道具体的工厂规则,而只是希望他们知道有一个统一的工厂即可。这样的设计有助于对外封装和简化调用者的使用,毕竟调用者可不想知道选择具体工厂的规则。

SSM框架学习笔记——Mybatis_第6张图片
以车厂为例,生产商不会把轿车、大巴车、警车、吉普车、救护车等车型都放在一
个车厂生产, 那样会造成车厂异常复杂, 从而导致难以管理和维护。所以, 生产商通常会
把它们按种类分为轿车厂、大巴车厂、警车厂、吉普车厂等分厂,每个种类下面有一些型
号的产品。但是对于客户而言, 只要告诉客服自己需要什么类型的车即可, 至于如何分配
给工厂那是客服的事情。

客户只是认为有一个能够生产各类车的工厂, 它能生成我所需要的产品, 这里工厂只
是一个虚拟的概念, 并不真实存在,它是通过车厂内部各个分厂去实现的, 这个虚拟工厂
被称为抽象工厂, 它的各个分厂称为具体工厂。为了统一, 需要制定一个接口规范
, 所有的具体工厂和抽象工厂都要实现这个接口。

SSM框架学习笔记——Mybatis_第7张图片

建造者模式

建造者模式属于对象的创建模式。可以将一个产品的内部表象(属性〉与产品的生成
过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。
SSM框架学习笔记——Mybatis_第8张图片
构建套票对象:

TicketHelper helper= new TicketHelper() ;
helper . buildAdult (” 成人票”) ;
helper . buildChildrenForSeat ( ” 有座儿童” ) ;
helper . buildchildrenNoSeat ( ” 无座儿童” ) ;
helper . buildElderly (” 老人票” );
helper . buildSoldier(” 军人票”) ;
Object ticket = TicketBuilder.builder(helper) ;

构建分成若干步,通过一步步构建信息,把一个复杂的对象构建出来。

MyBatis核心组件

持久层

持久层可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏(大部分的
重要数据都会有相关的备份机制),在断电或者其他情况下,重新开启系统仍然可以读取这
些数据。

SSM框架学习笔记——Mybatis_第9张图片

Mybatis优点

  • 不屏蔽SQL , 意味着可以更为精确地定位SQL 语句,可以对其进行优化和改造。
  • 提供强大、灵活的映射机制,方便Java 开发者使用。提供动态SQL 的功能,允许
    我们根据不同条件组装SQL。
  • 在MyBatis 中,提供了使用Mapper 的接口编程,只要一个接口和一个XML 就能创
    建映射器,进一步简化我们的工作,使得很多框架API 在MyBatis 中消失,开发者
    能更集中于业务逻辑。

Mybatis核心组件

  • SqlSessionFactoryBuilder (构造器):它会根据配置或者代码来生成SqISessionFactory ,采用的是分步构建的Builder模式。
  • SqlSessionFactory(工厂接口):依靠它来生成Sq!Session,使用的是工厂模式。
  • SqlSession (会话): 一个既可以发送SQL执行返回结果,也可以获取Mapper的接口。在现有的技术中, 一般我们会让其在业务逻辑代码中“消失”,而使用的是MyBatis 提供的SQLMapper 接口编程技术,它能提高代码的可读性和可维护性。
  • SQL Mapper (映射器): MyBatis 新设计存在的组件,它由一个Java 接口和XML
    文件(或注解) 构成,需要给出对应的SQL 和映射规则。它负责发送SQL去执行, 并返回结果。

SSM框架学习笔记——Mybatis_第10张图片

SqlSessionFactory(工厂接口)

  • 使用构造器SqlSessionFactoryBuilder生产SqlSessionFactory。
  • SqlSessionFactoryBuilder采用的是构建者模式,提供了一个Configuration类来控制分步。
  • 在Mybatis中,既可以使以使用读取XML配置的方式生成SqlSessionFactory,也可以使用JAVA代码生产。
  • 建议使用XML配置,Mybatis会读取配置文件,通过Configuration类对象构建整个Mybatis的上下文。
  • 注意,SqlSessionFactory是一个接口,它有两个实现类:SqlSessionManager和DefaultSqlSessionFactory。一般而言,由DefaultSqlSessionFactory去实现。
    SSM框架学习笔记——Mybatis_第11张图片
  • MyBatis的应用都是以一个SqlSessionFactory的实例为中心的,而SqlSessionFactory的唯一作用就是产生核心接口对象SqlSession。

使用XML构建SqlSessionFactory




  
   
      
  
  
  
    
       配置事务管理方式- JDBC
       配置数据库 POOLED代表使用Mybatis内部提供的连接池方式
        
        
        
        
      
    
  
  
  
    
     
  

创建SqlSessionFactory

SqlSessionFactory SqlSessionFactory = null;
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
     inputStream = Resources.getResourceAsStream(resource);
     SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch(IOException e){
  e.printStackTrace();
}

SqlSession

  • SqlSession是MyBatis中的核心接口
  • SqlSessionFactory是一个接口,它有两个实现类:SqlSessionManager和DefaultSqlSessionFactory。SqlSessionManager在多线程环境下使用,DefaultSqlSessionFactory在多线程环境下使用。
  • SqlSession作用类似于JDBC中的Connection对象,代表一个连接资源的启用。
  • 它的作用有三个:
    获取Mapper接口
    发送SQL给数据库
    控制数据库事务

创建方法:

SqlSession sqlSession = SqlSessionFactory.openSession();

控制事务伪代码:
SSM框架学习笔记——Mybatis_第12张图片

映射器

映射器是MyBatis中最重要、最复杂的组件。
由一个接口和对应的XML文件(或注解)组成。
它配置以下内容:

  • 描述映射规则。
  • 提供SQL语句,并可以配置SQL参数类型、返回类型、缓存刷新等信息。
  • 配置缓存。
  • 提供动态SQL。

在测试之前,首先定义一个POJO:

public class Role {

	private Long id;
	private String roleName;
	private String note;

	/** setter and getter **/
   ...
}

用XML实现映射器

创建映射器接口:

public interface RoleMapper {
	public Role getRole(Long id);
}

在mybatis-config.xml(即创建SqlSession的配置文件)中配置:

  
    
 

它的作用是引入一个XML文件。用XML方式创建映射器:





	


有了这两个文件(映射器接口和RoleMapper.xml),就完成了一个映射器的定义。

  • 元素中的属性namespace所对应的是一个全限定类名,Mybatis通过它找到该配置对应的接口。
  • select id, role_name as roleName, note from t_role where id = #{id}
    1. 引入映射器

    SSM框架学习笔记——Mybatis_第22张图片

    映射器

    映射器的配置元素:SSM框架学习笔记——Mybatis_第23张图片

    select

    SSM框架学习笔记——Mybatis_第24张图片

    在实际工作中,常用的是id, parameterType, resultType, resultMap, 如果要设置缓存,还会使用到flushCache, useCache.

    select简单实例

         
    
    • id:配合Mapper的全限定名,用于标识这条SQL
    • parameterType:表示这条SQL接受的参数类型,可以是系统定义或自定义的别名,如int,string,float等,也可以是类的全限定类名,如com.learn.ssm.chapter5.pojo.User.
    • resultType:表示这条SQL返回的结果类型,与parameterType一样,可以是系统定义或自定义的别名,也可以是类的全限定名。
    • #{firstName}:是被传递进去的参数

    此外,我们还需要定义接口方法才能使程序运行:

    public Integer countUserByFirstName(String firstName);
    

    自动映射和驼峰转换

    自动映射默认开启,在setting元素中可以配置自动映射和驼峰转换的开关。

    为了实现自动映射,首选要有POJO:

    public class Role {
    	private Long id;
    	private String roleName;
    	private String note;
    	/**setter and getter**/
    	....
    }
    

    如果编写的SQL列名和属性名保持一致,那么它就会形成自动映射。

    		
    

    原来的列名role_name被roleName代替了,这样就和POJO的属性名一致了,形成了自动映射。

    如果系统都严格按照驼峰命名法,如数据库字段为role_name, 则POJO属性名为roleName; 又如数据库字段为user_name, 则POJO属性名为userName, 那么只要在配置项把mapUnderscoreToCamelCase设置为true即可。如果这样做,那么上面的SQL可以改写成:

    select id, role_name, note from t_role where id = #{id} 
    

    传递多个参数

    假设要通过角色名称(role_name)和备注(note)对角色进行模糊查询,这样就需要两个参数了。

    map接口传递参数

    不推荐,省略。

    使用注解传递多个参数

    	public List findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
    
    	
    
    

    此时不要再给出parameterType属性,让MyBatis自动探索便可以了。

    通过Java Bean传递多个参数

    先定义一个参数的POJO:

    public class RoleParams {
    	private String roleName;
    	private String note;
    	/**setter and getter**/
    }
    

    此时接口方法定义为:

    public List findRolesByBean(RoleParams roleParam);
    

    mapper文件:

    	
    

    查询:

    	sqlSession = SqlSessionFactoryUtils.openSqlSession();
    	RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
    	RoleParams roleParam = new RoleParams();
    	roleParam.setNote("1");
    	roleParam.setRoleName("1");
    	List roles = roleMapper.findRolesByBean(roleParam);
    

    混合使用

    在某些情况下可能需要混合使用几种方法来传递参数。举个例子,查询一个角色,可
    以通过角色名称和备注进行查询,与此同时还需要支持分页,而分页的POJO 实现:

    public class RageParams {
    	private int start;
    	private int limit;
    	/**setter and getter**/
    }
    

    接口:

    public List findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam)
    
    	
    

    使用resultMap映射结果集

    SSM框架学习笔记——Mybatis_第25张图片

    分页参数 RowBounds

    待补充

    insert

    SSM框架学习笔记——Mybatis_第26张图片

    简单insert实例

    
    	insert into t_role(role_name,note) values (#{roleName},#{note})
    
    
    • id:标识这条SQL,结合命名空间(接口的全限定类名)让MyBatis能够找到它
    • parameterType:代表传入参数类型

    主键回填

    上方的简单插入语句没有插入id列,因为MySQL中的表格使用了自增主键,会为这条记录自动生成主键。

    有时候可能需要继续使用这个主键,用以关联其他业务,所以需要提取这个id(主键)。
    比如新增用户时,肯先会插入用户表的记录,然后插入用户和角色关系表,插入用户时如果没有办法取到用户的主键,那么就没有办法插入用户和角色关系表了,因此在这个时候要拿到对应的主键。

    在insert语句中有一个开关属性useGeneratedKeys,用来控制是否打开获得生成主键的功能,它的默认值是false。
    当打开这个开关时,还要配置其属性keyProperty或者keyColumn,告诉系统把生成的主键放入哪个属性中,如果存在多个主键,要用逗号隔开。

    
      insert into t_role(role_name, note) values(#{roleName},#{note})
      
    

    keyProperty代表将用哪个POJO的属性去匹配这个主键,这里是id, 说明它会把数据库生成的主键值赋值给POJO的id属性。

    自定义主键

    有时候主键的规则并不是自增的,比如将主键规则修改为:

    • 当角色表记录为空时, id 设置为1
    • 当角色表记录不为空时, id 设置为当前id加3

    此时需要使用selectKey元素进行支持。

    
    	
    	 		select if (max(id) = null , 1, max(id) + 3) from t_role
    	
    	insert into t_role(id, role_name, note) values(#{id}, #{roleName}), #{note})		
    
    • resultType说明要返回一个long型的结果集。
    • order设置为BEFORE,说明要在SQL前执行。

    update元素和delete元素

    
    	update t_role set role_name = #{roleName}, note = #{note}
    	where id = #{id}
    
    
    	delete from t_role where id = #{id}
    		
    

    完成后MyBatis会返回一个整数,标识影响了多少条数据。

    sql元素

    可以定义一条SQL的一部分,方便后面的SQL引用它。

    
    	id,role_name,note
    
    
    		
    

    resultMap元素

    元素构成

    
    	
    		
    		
    	
    	
    	
    	
    	
    		
    	
    			
    

    constructor元素用于配置构造方法,一个POJO可能不存在没有参数的构造方法,可以使用constructor进行配置。
    假设角色类RoleBean不存在没有参数的构造方法,它的构造方法声明为public RoleBean(Integer id, String roleName), 那么需要配置结果集:

    
    	
    	
    	
    

    id元素表示哪个列是主键,允许多个主键,多个主键则称为联合主键。result 是配置
    POJO 到SQL 列名的映射关系。result 元素和idArg 元素的属性:
    SSM框架学习笔记——Mybatis_第27张图片

    使用map存储结果集

    SSM框架学习笔记——Mybatis_第28张图片

    使用POJO存储结果集(常用)

    一方面可以所使用的自动映射,正如使用resultType属性一样。
    有时候更复杂的映射或者级联,可以使用select语句的resultMap属性配置映射集合。

    配置resultMap

    
    	
    	
    	
    	
    
    • id:这个resultMap的标识
    • type: 需要映射的POJO
    • 子标签id:表示这个对象的主键
    • property:代表POJO的属性名称
    • column:数据库SQL的列名

    这样,POJO就和数据库SQL的结果一一对应起来了。
    然后在mapper文件中的select元素中配置:

    	
    

    级联

    第五章待补充。。

    动态SQL

    SSM框架学习笔记——Mybatis_第29张图片

    if元素

    举例:根据角色名称(roleName)去查找角色,但是角色名称是一个选填条件,不填写时,就不要它作为条件查询。

    		
    

    注:where 后的 1=1 是为了if语句控制的后半段不执行时语句依然正确。

    choose , when , otherwise元素

    与if大同小异, 类似 switchl…case…default

    trim, where , set元素

    使用where标签以代替上面奇怪的 1=1条件:
    where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

    SSM框架学习笔记——Mybatis_第30张图片
    trim元素也能完成相同的功能:
    prefix代表语句前缀,prefixOverrides代表需要去掉哪种字符串
    SSM框架学习笔记——Mybatis_第31张图片
    set元素常用语更新字段
    SSM框架学习笔记——Mybatis_第32张图片

    foreach元素

    遍历集合,往往用语SQL中的In关键词。
    比如查找一个ID集合中所有ID对应的详细信息:
    SSM框架学习笔记——Mybatis_第33张图片

    用test属性判断字符串

    test的作用相当于判断真假,在大部分场景中,它都是用以判断空和非空。
    与if元素配合使用。

    bind元素

    bind 元素的作用是通过OGNL 表达式去自定义一个上下文变量,这样更方便使用。
    如果是MySQL 数据库,常常用到的是一个concat,它用“%”和参数相连。然而在Oracle 数据库则没有, Oracle 数据库用连接符号“||”,这样SQL 就需要提供两种形式去实现。但是有了bind 元素,就不必使用数据库的语言,而是使用MyBatis 的动态SQL 即可完成。
    使用bind进行模糊查询:
    SSM框架学习笔记——Mybatis_第34张图片
    同时,Mybatis也支持多个参数使用bind元素的用法:
    定义接口:

    public List findRole(@Param("roleName")String roleName, @Param("note")String note);
    

    定义映射文件:
    SSM框架学习笔记——Mybatis_第35张图片

    Mybatis底层与插件

    待学习补充。。

你可能感兴趣的:(SSM)