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容器自己通过配置找到插座。

<bean id="socket" class="Socket1" />
<bean id="user" class="xxx.User">
         <property name="socket" ref="socket" />
</bean>

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

<bean id="socket" class="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

在这里插入图片描述
在这里插入图片描述
Hibernate对POJO和表进行了全表映射。然后对POJO操作,从而对t_role表里的数据进行增删改查。代码里没有SQL,因为Hibernate会根据映射关系来生成对应的SQL,只需要操作POJO就能操作对应数据库中的表了。

MyBatis

与Hibernate不同,MyBatis不屏蔽SQL,程序员可以自己制定SQL规则,这样能更加精确地定义SQL。
在这里插入图片描述
resultMap元素用于定义映射规则,Mybatis在满足一定规则下,完成自动映射,增删改查对应四个元素。

扫描二维码关注公众号,回复: 9512245 查看本文章

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

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

mapper元素中的namespace属性,需要和一个接口的全限定类名一致,而里面SQL的id也需要和接口定义的方法完全保持一致。
映射文件(接口):
在这里插入图片描述
在这里插入图片描述

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<String> 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<String>();
        }

        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 的规范,所以初始化就简单了许
多。对于产品对象的创建,可以把一些特有产品规则写入工厂类中。

抽象工厂

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

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

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

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

在这里插入图片描述

建造者模式

建造者模式属于对象的创建模式。可以将一个产品的内部表象(属性〉与产品的生成
过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。
在这里插入图片描述
构建套票对象:

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

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

MyBatis核心组件

持久层

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

在这里插入图片描述

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去执行, 并返回结果。

在这里插入图片描述

SqlSessionFactory(工厂接口)

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

使用XML构建SqlSessionFactory

<?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>
  <typeAliases>
  <!-- 定义了一个别名role,它代表着com.learn.ssm.chapter3.pojo.Role这个类,这样我们就能在Mybatis上下文中使用别名来代替全限定类名了--> 
      <typeAlias alias="role" type="com.learn.ssm.chapter3.pojo.Role"/>
  </typeAliases>
  <!-- 数据库环境 -->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/> 配置事务管理方式- JDBC
      <dataSource type="POOLED"> 配置数据库 POOLED代表使用Mybatis内部提供的连接池方式
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/chapter3"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <!-- 映射文件 -->
  <mappers>
    <mapper resource="com/learn/ssm/chapter3/mapper/RoleMapper.xml"/>
    <mapper class="com.learn.ssm.chapter3.mapper.RoleMapper2"/> 
  </mappers>
</configuration>

创建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();

控制事务伪代码:
在这里插入图片描述

映射器

映射器是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的配置文件)中配置:

  <mappers>
    <mapper resource="com/learn/ssm/chapter3/mapper/RoleMapper.xml"/>
 </mappers>

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

<?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.learn.ssm.chapter3.mapper.RoleMapper">

	<select id="getRole" parameterType="long" resultType="role">
		select id,
		role_name as roleName, note from t_role where id = #{id}
	</select>

</mapper>

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

  • <mapper> 元素中的属性namespace所对应的是一个全限定类名,Mybatis通过它找到该配置对应的接口。
  • <select> 元素表明这是一条查询语句,属性id标识了这条SQL,属性parametreType表明传递给SQL的是一个long型参数,而resultType="role"表示返回的是一个role类型的返回值。而role是之前mybatis配置文件中配置的别名,代表com.leam.ssm.chapter3.pojo.Role。
  • #{id} 表示传递进去的参数。
  • 此处并没有配置SQL执行后和role的对应关系,它是如何映射的呢?其实这里采用的是一种被称为自动映射的功能,Mybatis在默认情况下提供自动映射, 只要SQL返回的列名能和POJO 对应起来即可。这里SQL 返回的列名id 和note 是可以和之前定义的POJO的属性对应起的, 而表里的列role_name通过SQL 别名的改写, 使其成为roleName, 也是和POJO对应起来的, 所以此时MyBatis 就可以把SQL 查询的结果通过自动映射的功能映射成为一个POJO 。
  • 【即此处select语句中的列名和POJO的属性相对应,Mybatis把查询的结果自动映射成了POJO】

小结:
在这里插入图片描述

SqlSession 发送SQL

有了映射器就可以通过Sq!Session 发送SQL 了。我们以getRole 这条SQL 为例看看如何发送SQL 。

Role role = (Role)sqlSession.selectOne("com.learn.ssm.chapter3.mapper.RoleMapper.getRole",1L);

selectOne方法代表使用查询并且只返回一个对象,而参数则是一个String对象和一个Object对象。
1L:这里是一个long 参数, long 参数是它的主键。
“com.learn.ssm.chapter3.mapper.RoleMapper.getRole”:String对象是由一个命名空间加上SQLid组合而成的,它完全定位了一条SQL,这样MyBatis就会找到对应的S QL。

用Mapper接口 发送SQL

SqlSession还可以获取Mapper接口,通过Mapper接口发送SQL

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);

通过sqlSession的getMapper方法获取一个Mapper接口就可以调用这个接口的方法了。
这里调用了RoleMapper接口的getRole()方法。

生命周期

SqlSessionFactoryBuilder

作用在于创建SqlSessionFactory ,创建成功后,就失去了作用,所以它只能存在于创建Sq!SessionFactory的方法中,而不要让其长期存在。

SqlSessionFactory

Sq!SessionFactory 可以被认为是一个数据库连接池,它的作用是创建SqlSession 接口对象。因为MyBatis 的本质就是Java 对数据库的操作,所以SqlSessionFactory 的生命周期存在于整个My Batis 的应用之中,所以一旦创建了SqlSessionFactory , 就要长期保存它,直至不再使用MyBatis 应用,所以可以认为SqlSessionFactory 的生命周期就等同于MyBatis 的应用周期。

由于SqlSe ssionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory ,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统密机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望Sq!SessionFactory 作为一个单例,让它在应用中被共享。

Mapper

Mapper 是一个接口,它由SqlSession 所创建,所以它的最大生命周期至多和SqlSe ssion保持一致,尽管它很好用,但是由于SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。
在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
代码略

MyBatis配置

<?xml version = ” 1. 0” encoding= ” UTF - 8 ” ?>
<configuration> <!一配置一>
	<properties/> < ! 一属性一>
	<settings/> < ! 一设置一>
	<typeAliases/> <! 一类型命名一>
	<typeHandlers/> < !一类型处理器一>
	<objectFactory/> < !一对象工厂一>
	<plugins/> <!一插件一>
	<environments> <! 一配置环境一>
			<environment> < ! 一环境变量一>
					<transactionManager/> < ! 一事务管理器一>
					<dataSource/> < !一数据源一>
			</environment>
	</environments>
	<databaseidProvider/> < !一数据库厂商标识一〉
	<mappers/> < ! 一映射器一〉
</configuration>

properties

给系统配置运行参数

使用property子元素

在这里插入图片描述
在这里插入图片描述

使用properties文件

在这里插入图片描述

使用程序传递方式传递参数

在这里插入图片描述

settings配置

大部分情况下使用默认值, 详细内容可以查表。

typeAliases

定义别名来代表全限定类名。
在Mybatis的初始化过程中,系统自动初始化了一些别名:
在这里插入图片描述
在这里插入图片描述
在mybatis-config.xml配置文件中定义:

  <typeAliases><!-- 别名 -->
      <typeAlias alias="role" type="com.learn.ssm.chapter3.pojo.Role"/>
  </typeAliases>

此外,MyBatis还支持扫描别名

  <typeAliases><!-- 别名 -->
      <typeAlias name="com.learn.ssm.chapter3.pojo"/>
  </typeAliases>

这样MyBatis会扫描这个包里的类,并将其首字母小写作为其别名,即Role类的别名是role.

typeHandler类型转换器

在JDBC 中, 需要在PreparedStatement 对象中设置那些已经预编译过的SQL 语句的参数。执行SQL 后,会通过ResultSet 对象获取得到数据库的数据,而这些,MyBatis 是根据数据的类型通过ψpeHandler 来实现的。

typeHandler的作用是承担jdbcType和javaType之间的相互转换。

大多数情况下,我们不需要配置typeHandler,对于特殊场景,可以自定义typeHandler.

ObjectFactory

当创建结果集时, MyBatis 会使用一个对象工厂来完成创建这个结果集实例。在默认
的情况下, MyBatis 会使用其定义的对象工厂一DefaultObjectFactory来完成对应的工作。

environments

作用是配置数据库信息,数据库信息分为事务管理器和数据源。

	<!-- 数据库环境 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="com.learn.ssm.chapter4.datasource.DbcpDataSourceFactory">
				<property name="driver" value="${database.driver}" />
				<property name="url" value="${database.url}" />
				<property name="username" value="${database.username}" />
				<property name="password" value="${database.password}" />
			</dataSource>
		</environment>
	</environments>

databaseIdProvider 数据库厂商标识

主要是支持多种不同厂商的数据库, 并不常用。

引入映射器

  1. 首先定义接口
public interface RoleMapper{
      public Role getRole(Long id);
}
  1. 定义Mapper映射规则和SQL语句
....
<mapper namespace="com.learn.ssm.chapter4.mapper.RoleMapper">
	<select id="getRole" parameterType="long" resultType="role">
	select id, role_name as roleName, note from t_role
	where id = #{id}
	</select>
</mapper>	
  1. 引入映射器

在这里插入图片描述

映射器

映射器的配置元素:在这里插入图片描述

select

在这里插入图片描述
在这里插入图片描述
在实际工作中,常用的是id, parameterType, resultType, resultMap, 如果要设置缓存,还会使用到flushCache, useCache.

select简单实例

<select id="countUserByFirstName” parameterType="string" resultType="int">
     select count(*) total from t_user
     where user_name like concat(#{firstName}, '%')
</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列名和属性名保持一致,那么它就会形成自动映射。

<select id="getRole" parameterType="long"
		resultType="com.learn.ssm.chapter5.pojo.Role">
		select id,role_name as roleName,note from t_role where id = #{id}
</select>		

原来的列名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<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
	<select id="findRolesByAnnotation" resultType="role">
		select id,
		role_name as roleName, note from t_role
		where role_name like
		concat('%', #{roleName}, '%')
		and note like concat('%', #{note}, '%')
	</select>

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

通过Java Bean传递多个参数

先定义一个参数的POJO:

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

此时接口方法定义为:

public List<Role> findRolesByBean(RoleParams roleParam);

mapper文件:

	<select id="findRolesByBean" parameterType="com.ssm.chapter5.param.RoleParams"
		resultType="role">
		select id, role_name as roleName, note from t_role
		where role_name like concat('%', #{roleName}, '%')
		and note like concat('%',#{note}, '%')
	</select>

查询:

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

混合使用

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

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

接口:

public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam)
	<select id="findByMix" resultType="role">
		select id, role_name as
		roleName, note from t_role
		where role_name like
		concat('%',
		#{params.roleName}, '%')
		and note like concat('%', #{params.note}, '%')
		limit #{page.start}, #{page.limit}
	</select>

使用resultMap映射结果集

在这里插入图片描述

分页参数 RowBounds

待补充

insert

在这里插入图片描述

简单insert实例

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

主键回填

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

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

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

<insert id="insertRole" parameterType="role"
  useGenerateKeys="true" keyProperty="id">
  insert into t_role(role_name, note) values(#{roleName},#{note})
</insert>  

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

自定义主键

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

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

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

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

update元素和delete元素

<update id="updateRole" parameterType="role">
	update t_role set role_name = #{roleName}, note = #{note}
	where id = #{id}
</update>
<delete id="deleteRole" parameterType="long">
	delete from t_role where id = #{id}
</delete>		

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

sql元素

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

<sql id="roleCols">
	id,role_name,note
</sql>

<select id="getRole" parameterType="long" resultMap="roleMap">
	select <include refid="roleCols" /> from t_role where id = #{id}
</select>		

resultMap元素

元素构成

<resultMap>
	<constructor>
		<idArg/>
		<arg/>
	</constructor>
	<id/>
	<result />
	<collection/>
	<discriminator>
		<case/>
	</discriminator>
</resultMap>			

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

<constructor>
	<idArg column="id" javaType="int" />
	<arg column="role_name" javaType="string" />
</constructor>	

id元素表示哪个列是主键,允许多个主键,多个主键则称为联合主键。result 是配置
POJO 到SQL 列名的映射关系。result 元素和idArg 元素的属性:
在这里插入图片描述

使用map存储结果集

在这里插入图片描述

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

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

配置resultMap

<resultMap id="roleResultMap" type="com.learn.chapter4.pojo.Role">
	<id property="id" column="id" />
	<result property="roleName" column="role_name" />
	<result property="note" column="note" />
</resultMap>	
  • id:这个resultMap的标识
  • type: 需要映射的POJO
  • 子标签id:表示这个对象的主键
  • property:代表POJO的属性名称
  • column:数据库SQL的列名

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

<select parameterType="long"  id="getRole" resultMap="roleResultMap">
	select id, role_name, note from t_role where id = #{id}
</select>	

级联

第五章待补充。。

动态SQL

在这里插入图片描述

if元素

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

<select id="findRoles" parameterType="string" resultMap="roleResultMap">
	select role_no, role_name, note from t_role where 1=1
	<if test="roleName != null and roleName !=''">
		and role_name like concat('%', #{roleName}, '%')  <!-- if 控制了这条语句是否执行-->
	</if>
</select>		

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

choose , when , otherwise元素

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

trim, where , set元素

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

在这里插入图片描述
trim元素也能完成相同的功能:
prefix代表语句前缀,prefixOverrides代表需要去掉哪种字符串
在这里插入图片描述
set元素常用语更新字段
在这里插入图片描述

foreach元素

遍历集合,往往用语SQL中的In关键词。
比如查找一个ID集合中所有ID对应的详细信息:
在这里插入图片描述

用test属性判断字符串

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

bind元素

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

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

定义映射文件:
在这里插入图片描述

Mybatis底层与插件

待学习补充。。

发布了40 篇原创文章 · 获赞 1 · 访问量 1105

猜你喜欢

转载自blog.csdn.net/weixin_44495162/article/details/100972247