MyBatis详解、静态代理与动态代理

  • Post author:
  • Post category:其他



目录


代理模式:


代理模式的作用:


代理模式的分类:


什么是静态代理:


静态代理实现:


解决耦合的普遍方法——面向接口编程(重要):


静态代理的弊端:


JDK动态代理:


JDK动态代理用到的类和接口:


(1)Proxy类:


(2)Method类:


(3)InvocationHandler接口:


演示案例:


CGLib动态代理:


动态代理的好处:


什么是三层架构:


为什么需要使用三层架构?


常用的框架SSM:


什么是框架:


什么是MyBatis框架:


数据库连接池:


MyBatis框架的结构:


添加框架的步骤(所有的框架均是如此):


Mybatis的dtd文件解读:


dtd文件的规则定义:


驼峰映射:


Mapper文件实参、返回值类型别名:


MyBatis对象分析:


Resources 类


SqlSessionFactoryBuilder 类


SqlSessionFactory 接口


SqlSession 接口


Mybatis中动态代理存在意义:


动态代理的实现规范:


动态代理情况下mapper.xml文件的注册:


Mybatis动态代理代码示例:


#{}与${}的区别:


#{}占位符:


${}字符串拼接:


优化后的模糊查询(以后都要使用这种方式):


${}字符串替换:


@Param注解:


@注解简单类型(8种基本数据类型(包括封装类)+String):


@Param注解JavaBean对象:


插入返回主键值:


使用 JDBC 方式返回主键自增的值:


使用 selectKey 返回主键的值:


UUID:


动态sql:


代码复用:


动态查询:


动态修改:


Foreach标签:


批量查询:


批量删除:


批量增加:


批量修改:


指定参数位置:


入参是map(重点掌握) :


返回值为一行map:


返回值为多行map:


列名和类中成员变量名称不一致:


使用别名进行成员变量和列名的映射:


使用resultMap进行成员变量和列名的映射:


表之间的关联关系:


一对多关联关系:


针对一对多查询的改进:


多对一关联关系:


针对多对一查询的改进:


一对一关联:


多对多关联:


事务:


缓存:


代码验证一级缓存:


ORM:


代理模式:

目标对象不可访问,通过代理对象增强功能访问。

生活中的代理例子:

房东                     ===>目标对象

房屋中介              ===>代理对象

你、我                 ===>客户端对象

服务生产厂          ===>目标对象

门店(旗舰店)        ===>代理对象

你、我                 ===>客户端对象

开发中的代理例子:

运营商(电信,移动,联通)                                ====>目标对象

第三方公司                                                  ====>代理对象

开发的应用程序需要发送短信的功能          ===>客户端对象

代理模式的作用:

1、控制目标对象的访问。

2、增强功能。

代理模式的分类:

1、静态代理。

2、动态代理,又为JDK动态代理,CGLib动态代理(子类代理)。

什么是静态代理:

它是代理模式的一种,它具备以下特点:

1、目标对象和代理对象实现同一个业务接口。

2、目标对象必须实现接口。

3、代理对象在程序运行前就已经存在。


4、能够灵活的进行目标对象的切换,却无法进行功能的灵活处理(使用动态代理解决此问题)。

静态代理实现:

业务功能:请明星进行节目表演。

明星刘德华:目标对象(无法直接访问)。

刘德华助理:代理对象(我们可以访问,他还可以跟明星对接)。

我们:客户端对象。


代码实现:

/**
 * 业务接口,规定唱歌的业务功能
 */

public interface Service {
    void sing();
}



/**
 * 客户端对象。通过代理对象与目标对象对接。
 */

public class test {
    public static void main(String[] args) {
        Service agent1 = new Agent(new SuperStarLiu());
        // 通过含参的构造函数向Agent类传递目标对象类的实例对象
        agent1.sing();
        Service agent2 = new Agent(new SuperStarZhou());
        agent2.sing();
    }
}



/**
 * 代理对象:助理,是客户端对象与目标对象的对接桥梁
 */

// 目标对象和代理对象实现同一个业务接口。
public class Agent implements Service{

    // 类中成员变量类型设计为接口类型
    public Service taget;

    // 传入目标对象,形参类型设计为接口类型
    public Agent(Service taget){
        this.taget=taget;
    }

    @Override
    public void sing() {
        System.out.println("预定时间...");
        System.out.println("预定场地...");

        // SuperStarLiu liu = new SuperStarLiu();
        // liu.sing();
        // SuperStarZhou zhou = new SuperStarZhou();
        // zhou.sing();
        // 这样代码就写死了,耦合度很高。

        // 面向接口编程,接口指向实现类。
        taget.sing();
        // tager是目标对象类的一个实例对象,根据不同目标对象在自己类对接口sing()方法的实现,可输出不同的语句
        // 过程中只需改动目标对象类的代码,不需要改动该类的具体代码,耦合度降低

        System.out.println("结算费用...");
    }
}



/**
 * 目标对象:周杰伦。实现业务接口中的功能,进行唱歌表演
 */

public class SuperStarZhou implements Service{
    @Override
    public void sing() {
        System.out.println("我是周杰伦,我正在表演唱歌...");
    }
}



/**
 * 目标对象:刘德华。实现业务接口中的功能,进行唱歌表演
 */

public class SuperStarLiu implements Service{
    @Override
    public void sing() {
        System.out.println("我是刘德华,我正在表演唱歌...");
    }
}



能够灵活的进行目标对象的切换(华仔和杰伦之间),却无法进行功能的灵活处理(只能是唱歌)。


解决耦合的普遍方法——面向接口编程(重要):

1、类中的成员变量类型设计为接口类型。

2、方法的形参类型设计为接口类型。

3、方法的返回值类型设计为接口类型。

4、调用时接口指向实现类。

静态代理的弊端:

上述演示的静态代理中,倘如接口中的抽象方法有所增加,那么其

实现类(目标对象类、代理对象类)以及测试类

也必须作出相应的更改,耦合度较高,这时候就需要用到我们的动态代理来解决这个问题。

JDK动态代理:

1、目标对象必须实现业务接口,而JDK代理对象不需要实现业务接口。(静态代理的目标对象和代理对象实现同一个业务接口)

2、JDK动态代理的对象在程序运行前不存在,在程序运行时动态的在内存中构建。(静态代理的代理对象在程序运行前就已经存在)

3、JDK动态代理灵活的进行业务功能的切换

4、jdk动态代理能够代理的只是接口中的方法,实现类(目标对象)本身的方法无法代理。

JDK动态代理用到的类和接口:

它是使用现在的工具类完成JDK动态实现.

(1)Proxy类:

它是java.lang.reflect.Proxy包下的类. 它有一个方法Proxy.newProxyInstance(…..)专门用来生成动态代理对象.

public static Object newProxyInstance(ClassLoader loader,  //类加载器
                                      Class<?>[] interfaces,   //目标对象实现的所有接口
                                      InvocationHandler h  //它就类似于Agent的功能,代理的功能和目标对象的业务功能调用在这)
    throws IllegalArgumentException {...}

(2)Method类:

反射用的类,用来进行目标对象的方法的反射调用.

method.invoke();        // 反射调用目标方法

(3)InvocationHandler接口:

它是实现代理和业务功能的,我们在调用时使用匿名内部实现。类似于Agent的功能。

演示案例:

/**
 *  业务接口
 */
public interface Service {
    void sing();

    // 如果接口有新增的方法,那么代理对象的代码不用改,目标对象和测试程序改即可
    String show(int age);
}
/**
 *  目标对象:刘德华
 */
public class SuperStarLiu  implements Service {
    @Override
    public void sing() {
        System.out.println("我是刘德华,我正在表演唱歌........");
    }

    // 如果接口有新增的方法,那么代理对象的代码不用改,目标对象和测试程序改即可
    @Override
    public String show(int age) {
        System.out.println("刘德华的show........."+age);
        return "liu";
    }

    //此方法不能被代理(不以增强功能)
    public void one(){
        System.out.println("one............");
    }
}
/**
 *  目标对象:周杰伦
 */
public class SuperStarZhou implements Service {
    @Override
    public void sing() {
        System.out.println("我是周润发,我正在表演唱歌........");
    }

    // 如果接口有新增的方法,那么代理对象的代码不用改,目标对象和测试程序改即可
    @Override
    public String show(int age) {
        System.out.println("周杰伦的show........."+age);
        return "zhou";
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *
 */
public class ProxyFactory {
    //类中的成员变量设计为接口,目标对象
    Service target;

    //传入目标对象
    public ProxyFactory(Service target){
        this.target = target;
    }

    //返回动态代理对象
    public Object getAgent(){
        // 所有的代理对象都可以返回,所以返回值是Object类型
        return Proxy.newProxyInstance(
                // ClassLoader loader, 类加载器,完成目标对象的加载
                target.getClass().getClassLoader(),

                // Class<?>[] interfaces,目标对象实现的所有接口
                target.getClass().getInterfaces(),

                // InvocationHandler h,它就类似于Agent的功能,代理的功能和目标对象的业务功能调用在这,
                // 代理对象功能的一个实现 ,实现的方法是传入的是匿名内部类/lambda表达式
                new InvocationHandler() {   // 中文名:调用处理程序
                    @Override
                    public Object invoke(
                            //创建代理对象
                            Object proxy,   // 这个参数不是给我们调用的
                            //method就是目标方法sing(),show()
                            Method method,  // 当作已经通过反射机制获得了方法就行
                            //目标方法的参数
                            Object[] args) throws Throwable {
                        //代理功能
                        System.out.println("预订时间........");
                        //代理功能
                        System.out.println("预订场地........");

                        //主业务功能实现
                        //target.sing();还是写死了方法的调用, 不成
                        //sing(),show(),one()
                        Object obj = method.invoke(target,args); // 反射机制调用target(目标对象)的method方法,实参为args。
                        // 切记:该行代码的返回值是 method通过反射指向的方法 的返回值,可能是任何类型

                        //代理功能
                        System.out.println("结算费用........");

                        return obj;  // 切记:这里返回的是所调用方法的返回值,可能是任意类型
                    }
                }
        );
    }
}
/**
 *  测试类
 */
public class MyTest {

    public static void main(String[] args) {

        /*
            案例一
         */
        ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
        // 通过构造方法传参到ProxyFactory类中的target属性

        // 获得动态代理对象(助理/中间者)
        Service agent = (Service) factory.getAgent();
        // 相当于静态代理一样,代理对象下都有一个跟目标对象一样的接口方法的实现(sing),所以可直接调用

        agent.sing();



        /*
            案例二
         */
        ProxyFactory factory2 = new ProxyFactory(new SuperStarZhou());
        Service agent2 = (Service) factory.getAgent();
        String showReturn = agent2.show(60);
        System.out.println(showReturn);


        /*
            案例三
         */
        ProxyFactory factory3 = new ProxyFactory(new SuperStarLiu());
        Service agent3 = (Service) factory3.getAgent();
        System.out.println(agent3.getClass());
        // 注意,返回的动态代理对象不会是SuperStarLiu的类型,而是class com.sun.proxy.$Proxy0类型
        Service service = new SuperStarLiu();
        System.out.println(service.getClass());
        // class com.bjpowernode.service.impl.SuperStarLiu


        /*
            案例四,jdk动态代理能够代理的只是接口中的方法,实现类本身的方法无法代理。
         */
        ProxyFactory factory4 = new ProxyFactory(new SuperStarLiu());
        SuperStarLiu agent4 = (SuperStarLiu) factory4.getAgent();
        // agent.one();
// Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.bjpowernode.service.impl.SuperStarLiu
        // 类型转换异常,因为经过工厂方法后,代理对象的功能已经被增强(多了预定时间、预定地点、结算费用等方法),类型已经变成了class com.sun.proxy.$Proxy0
        // 与SuperStarLiu没有了瓜葛,当然不能强转
        // 但可以转为接口类型,因为动态代理对象相当于是接口的一个新的实现。!!!!!!!!!!!!!!!


    }

}

可以在测试类中任意调用目标对象类实现接口的任一方法(除接口外的方法不行),JDK动态代理更改测试类比静态代理更改底层类更优。

CGLib动态代理:

又称为子类代理。通过动态的在内存中构建子类对象,重写父类的方法进行代理功能的增强。如果目标对象没有实现接口,则只能通过CGLib子类代理来进行功能增强。

子类代理是对象字节码框架ASM来实现的。

注意:

(1)需要spring-core-5.2.5.jar依赖即可。引入功能包后,就可以在内存中动态构建子类

(2)

被代理的类不能为


final

, 否则报错。(无法继承,无法构建子类)

(3)

目标对象的方法如果为


final/static,

那么就不会被拦截,即不会执行目标对象额外的业务方法。(静态方法没有多态)

(4) 代码实现结构:


ProxyFactory.java:

public class ProxyFactory implements MethodInterceptor {
        //目标对象
        private Object target;
        //传入目标对象
        public ProxyFactory(Object target){
                 this.target=target;
        }              

        //Cglib采用底层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
        @Override
        public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
                 //代理对象的功能
                 System.out.println("预定场地............");
                 //调用目标对象的方法
                 Object returnValue=method.invoke(target, arg2);        
                 //代理对象的功能
                 System.out.println("结帐走人............");                
                 return returnValue;
        }      

        //生成代理对象
        public Object getProxyInstance(){         
                 //1.使用工具类
                 Enhancer en=new Enhancer();
                 //2.设置父类
                 en.setSuperclass(target.getClass());
                 //3.设置回调函数
                 en.setCallback(this);
                 //4.创建子类(代理)对象
                 return en.create();
        }
}     


测试类中:

public void testCglibProxy(){          
    SuperStar proxy=(SuperStar) new ProxyFactory(superStar).getProxyInstance();
    proxy.sing();
}

动态代理的好处:

静态代理中,倘如接口中的抽象方法有所增加,那么其

实现类(目标对象类、代理对象类)以及测试类

也必须作出相应的更改,耦合度较高。而对于动态代理来说,即便接口中的抽象方法有所增加,代理对象的代码也不用修改,只需修改目标对象的实现和测试类代码即可,降低了耦合度。

换言之,代理对象不再需要实现接口。(通过反射完成调用)

什么是三层架构:

是在项目开发中遵循的一种开发模式。

(1)界面层:用来接收客户端的输入,调用业务逻辑层进行功能处理,返回结果给客户端。过去的servlet就是界面层的功能。

(2)业务逻辑层:用来进行整个项目的业务逻辑处理,向上为界面层提供处理结果,向下问数据访问层要数据。

(3)数据访问层:专门用来进行数据库的增删改查操作,向上为业务逻辑层提供数据。

各层之间的调用顺序是固定的,不允许跨层访问。界面层<——->业务逻辑层<——>数据访问层

为什么需要使用三层架构?

  1. 结构清晰、耦合度低,  各层

    分工明确
  2. 可维护性高,可扩展性高

  3. 有利于标准化

  4. 开发人员可以只关注整个结构中的其中某一层的功能实现

  5. 有利于各层逻辑的复用

常用的框架SSM:

Spring:它是整合其它框架的框架,它的核心是IOC和AOP。它由20多个模块构成,在很多领域都提供了很好的解决方案,是一个大佬级别的存在。

SpringMVC:它是Spring家族的一员。专门用来优化控制器(Servlet),提供了极简单数据提交,数据携带,页面跳转等功能。

MyBatis:是持久化层的一个框架。用来进行数据库访问的优化,专注于sql语句,极大的简化了JDBC的访问。

什么是框架:

框架是一个半成品软件。将所有的公共的,重复的功能解决掉,帮助程序快速高效的进行开发。它是可复用,可扩展的。

什么是MyBatis框架:

MyBatis 本是 apache 的一个开源项目iBatis,2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis  。2013 年 11 月迁移到 Github,最新版本是 MyBatis 3.5.7 ,其发布时间是 2021 年 4月 7日。

数据库连接池:

假如没有连接池,那么我们在使用JDBC程序的时候,就会反复的创建连接,销毁连接,大部分时间都会耗费在连接的操作上面,这样做比较消耗资源,并不能做到连接的反复利用。这样做如果这个程序的用户人数很多,那么对性能的影响就会很大。所以为了优化性能,就可以使用数据库连接池,连接池中都是一些已经连接上数据库的对象,每次需要连接对象时可直接从连接池里面获取连接,使用完了连接直接放回连接池,做到连接的反复使用。

MyBatis框架的结构:

1、mybatis配置:SqlMapConfig.xml。此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。 mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂。

3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

添加框架的步骤(所有的框架均是如此):

(1)、添加依赖。

(2)、添加配置文件。


具体步骤:


1、新建库,建表

2、新建maven项目。

3、修改pom.xml文件,添加MyBatis的依赖,添加mysql的依赖

4、修改pom.xml文件,添加资源文件指定

5、在idea中添加数据库的可视化

6、添加jdbc.properties属性文件(数据库的配置)

(具体访问哪个数据库在这里配置)


7、添加SqlMapConfig.xml文件,MyBatis的核心配置文件

8、创建实体类Student,用来封装数据

9、添加完成学生表的增删改查的功能的StudentMapper.xml文件

10、创建测试类,进行功能测试

1、新建数据库,建表。

CREATE DATABASE ssm DEFAULT CHARSET utf8;

use ssm;

CREATE TABLE `student` (
`id` int(11)  AUTO_INCREMENT primary key ,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into student(name,email,age) values('张三','zhangsan@126.com',22);
insert into student(name,email,age) values('李四','lisi@126.com',21);
insert into student(name,email,age) values('王五','wangwu@163.com',22);
insert into student(name,email,age) values('赵六','zhaoliun@qq.com',24);
select * from student;

2、新建maven项目。

3、修改pom.xml文件,添加MyBatis的依赖,添加mysql的依赖。

    <!--添加MyBatis框架的依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>

    <!--添加mysql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>

4、修改pom.xml文件,添加资源文件指定。

	<build>
        <resources>
			<!--将src/main/resources目录下的所有资源文件打包-->
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.*</include>
				</includes>
                <!—filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
                <filtering>false</filtering>
			</resource>
            <!--同时也将src/main/java目录下的xml文件打包-->
			<resource>
				<directory>src/main/java</directory>
				<includes>
                    <!-- **表示java路径下的所有目录(文件夹),*.xml表示所有xml文件-->
					<include>**/*.xml</include>
				</includes>
 
                <!—filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
                <filtering>false</filtering>
			</resource>
 
		</resources>
 
	</build>

5、在idea中添加数据库的可视化:

6、添加jdbc.properties属性文件(数据库的配置):

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
// com.mysql.jdbc.Driver是mysql-connector-java 5版本的驱动名称,5版本之后替换为com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

7、添加SqlMapConfig.xml文件,属于MyBatis的核心配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--读取属性文件(jdbc.properties)
      属性:
         <properties resource="xxx" 或 url="xxx"></properties>

         resources:从resources目录下找指定名称的文件加载
         url:使用绝对路径加载属性文件
    -->
    <properties resource="jdbc.properties"></properties>

    <!--设置日志输出底层执行的代码-->
    <!--name和value是固定的,STDOUT_LOGGING 表示控制台日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--注册实体类的别名,注册完后Mapper文件就可以使用别名代替完整类名-->
    <!--别名大小写不敏感-->
    <typeAliases>
        <!--单个实体类别名注册-->
        <!--<typeAlias type="com.bjpowernode.pojo.Student" alias="student"></typeAlias>-->
        
        <!--批量注册别名。因为在公司的项目中一个包下可能有几百个类,如果都注册别名需要写几百行的注册别名,不实用,可以批量注册。
          别名是类名的驼峰命名法(规范)
        -->
        <package name="com.bjpowernode.pojo"></package>
    </typeAliases>

    <!--配置数据库的环境变量(数据库连接配置)
       default:使用下面的environment标签的id属性进行指定配置
    -->
    <environments default="development">
    <!-- environment的id是提供给environments的default属性进行快速的配置指定的-->

        <!--(开发时)在公司使用的数据库配置-->
        <environment id="development">

            <!--配置事务管理器
               type:指定事务管理的方式
                  JDBC:事务的控制交给程序员处理
                  MANAGED:由容器(Spring)来管理事务
            -->
            <transactionManager type="JDBC"></transactionManager>

            <!--配置数据源
               type:指定不同的配置方式
                  JNDI:java命名目录接口,在服务器端进行数据库连接池的管理
                  POOLED:使用数据库连接池
                  UNPOOLED:不使用数据库连接池
            -->
            <dataSource type="POOLED">

                <!--配置数据库连接的基本参数
                    private String driver;
                    private String url;
                    private String username;
                    private String password;
                -->
                <property name="driver" value="${jdbc.driverClassName}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>

        </environment>

    <!--
        在家的数据库配置
        <environment id="home">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>

        上线后的数据库配置&ndash;&gt;
        <environment id="online">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>
    -->
    </environments>

    <!--注册mapper.xml文件
       resource:从resources目录下找指定名称的文件注册
       url:使用绝对路径注册
       class:动态代理方式下的注册
    -->
<!--    需要多少个mapper注册多少个mapper-->
    <mappers>
        <mapper resource="StudentMapper.xml"></mapper>
    </mappers>

<!--
   <!--注册mapper.xml文件-->
    <mappers>

        <!--绝对路径注册-->
        <mapper url="/"></mapper>

        <!--非动态代理方式下的注册(StudentMapper.xml文件在resources文件夹下)-->
        <mapper resource="StudentMapper.xml"></mapper>

        <!--动态代理方式下的单个mapper.xml文件注册-->
        <mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>

        <!--批量注册-->
        <package name="com.bjpowernode.mapper"></package>

    </mappers> 

-->
</configuration>



注意注意注意注意!!!!



完整类名/完全限定名 是从Java文件夹下开始数的而不是src。完全限定名 即 含包名的类名。从src开始的叫类路径!!!!


注意:mybatis配置文件内容和顺序,必须为以下顺序,否则会出现错误

<configuration><!--配置-->
	<properties/><!--属性-->
	<settings/><!--设置-->
	<typeAliases/><!--类型别名--> 
	<typeHandlers/><!--类型处理器--> 
	<objectFactory/><!--对象工厂-->  
	<plugins/><!--插件--> 
	<environments><!--配置环境--> 
		<environment><!--环境变量--> 
		<transactionManager/><!--事务管理器--> 
			<dataSource/><!--数据源--> 
		</environment>
	</environments>
	<databaseidProvider/><!--数据库厂商标识-->  
	<mappers/><!--映射器--> 
</configuration>

如果我们想查看访问数据库时底层执行的一个代码,可以设置日志输出底层的代码。


Parameters是SQL语句的入参,如果使用 ${} 则入参不会显示(直接字符串拼接了)(存在SQL注入的风险)。

8、针对所要查询的表格,创建表格的实体类Student,用来封装查询到的数据。

package com.bjpowernode.pojo;

/**
 * 实体类Student
 */
public class Student {
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Student(String name, String email, Integer age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public Student(Integer id, String name, String email, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public Student() {
    }
}


注意:实体类的所有属性都应该采用包装类而不是基本数据类型。


例如,在一个成绩查询系统中,有的学生会出现考0分,而有的学生缺考,这就出现了0和NULL两种不同的属性值,如果我们采用基本数据类型int,那么即便在数据库中属性值为NULL,在经过Mybatis查询后,如果赋值给int类型也只会赋值0而非NULL,就无法区分0分和缺考的情况。


(防止初始化默认值为0从而与赋值0无法区分)


再者,当我们在使用mybatis的时候,mybatis会判断参数属性是否为空null,而我们实体类中的某个属性如果使用基本数据类型,如int,默认为0,那mybatis就无法对这个参数进行判断。



9、添加完成学生表的增删改查的功能的StudentMapper.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:是整个文件的大标签,用来开始和结束xml文件
  属性:
     namespace:指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的id属性
     假设我有一个StudentMapper.xml,还有一个TeacherMapper.xml,就需要通过namespace进行区分。
     namespace的值可以任意定义。
-->
<mapper namespace="zar">
    <!--
      完成查询全部学生的功能,类似于Java的方法。

      若定义的是Java方法,则方法定义为 List<Student> getALL();
        resultType:指定查询返回的结果集的类型。如果返回的是集合,则resultType必须是泛型的类型!!!
        因为如果resultType="List<Student>",就相当于拿一个list集合把所有的数据全装进来然后返回,可这样子数据就乱了不能一一对应上了,
        所以必须这种情况下必须使用泛型的类型,即Student,一个一个Student装起来返回,而不是一堆一堆Student一股脑返回,避免了混乱的问题。

        parameterType:如果有参数,则通过它来指定参数的类型


    -->
    <select id="getAll" resultType="student" >
       select id,name,email,age
       from student
    </select>

    <!--
      按主键id查询学生信息
      Student getById(Integer id);
    -->
    <select id="getById" parameterType="int" resultType="student">
        select id,name,email,age
        from student
        where id=#{id}
    </select>

    <!--
      按学生名称模糊查询
      List<Student> getByName(String name);
    -->
    <select id="getByName" parameterType="string" resultType="student">
        select id,name,email,age
        from student
        where name like '%${name}%'
    </select>


<!--增删改的方法中,都会带有返回值,返回受影响的行数。默认的,不需要设置,所以增删改没有resultType-->

    <!--
      增加学生
      int insert(Student stu);
      实体类:
        private Integer id;
        private String name;
        private String email;
        private Integer age;
    -->
    <insert id="insert" parameterType="student">
<!--不需要#{stu.name},直接写变量即可,系统会自动识别stu对象中的该变量的值。-->
<!--而且在Java程序中实参也只用写一个,就是对象即可,不用“对象.变量”-->
        insert into student (name,email ,age) values(#{name},#{email},#{age})
    </insert>

    <!--
      按主键删除学生
      int delete(Integer id);
    -->
    <delete id="delete" parameterType="int" >
        delete from student where id=#{id}
    </delete>

    <!--
      更新学生
      int update(Student stu);
    -->
    <update id="update" parameterType="student">
<!--不需要#{stu.name},直接写变量即可,系统会自动识别stu对象中的该变量的值。-->
<!--而且在Java程序中实参也只用写一个,就是对象即可,不用“对象.变量”-->
        update student set name=#{name},email=#{email},age=#{age}
        where id=#{id}
    </update>
</mapper>


StudentMapper.xml文件中的SQL语句不需要写分号!







xml文件的如果存在类似 user




name=#{userName} 这样的语句,前一个username是MySQL的列名,因为MySQL不区分大小写,所以name的首字母大小写均可,而后一个userName是实体类中的属性,因为Java的命名规范是驼峰命名法,这就导致了Name的首字母是大写,如果这里不写大写就会出错了!!!!


parameterType:


可以不写。但是多个入参需要在mapper接口中采用注解的方式将参数注解进去。


(数组、集合属于单个入参,可以不写注解不写


parameterType


)(但最好不要嫌麻烦)



(注意,resultType时如果返回集合需要写集合的泛型。但parameterType如果是集合那么类型就是集合,而不是泛型。



resultType写泛型是为了不让对象的属性全都混乱地怼进集合里区分不开,而parameterType是集合的话对象的属性已经整齐地排好了,不会混乱)



resultType简单类型可以省略,其余不可省略。

10、创建测试类,进行功能测试:

package com.bjpowernode.test;

import com.bjpowernode.pojo.Student;
import com.sun.org.apache.bcel.internal.generic.FADD;

import org.apache.ibatis.io.Resources;
// 注意,这个包的org.apache.ibatis.io,别选错了

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;

    @Before  //在所有的@Test方法执行前先执行的代码,简化代码量。
    public void openSqlSession() throws IOException {
        // 使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        // getResourceAsStream()方法自动会去不用Resources配置文件夹下找配置文件,不需要路径,只写名称和后缀即可。

        // 创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

        // 取出sqlSession的对象
        sqlSession = factory.openSession();
    }

    @After  //在所有的@Test方法执行后先执行的代码,简化代码量。
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testGetAll() throws IOException {
        //完成查询操作
        List<Student> list = sqlSession.selectList("zar.getAll");
        list.forEach(student -> System.out.println(student));
    }

    @Test
    public void testGetById() throws IOException {
        //按主键查学生
        //第二个是实参
        Student stu = sqlSession.selectOne("zar.getById",3);
        System.out.println(stu);
    }

    @Test
    public void testGetByName()throws IOException{

        //按名字模糊查询
        //第二个是实参
        List<Student> list = sqlSession.selectList("zar.getByName","李");
        list.forEach(student -> System.out.println(student));

    }

    @Test
    public void testInsert()throws IOException{

        //插入数据
// 配置文件中不需要#{stu.name},直接写变量即可,系统会自动识别stu对象中的该变量的值。而在Java程序中实参也只用写一个,就是对象即可,不用“对象.变量”
       int num = sqlSession.insert("zar.insert",new Student("haha666","haha@126.com",23));
// 因为这里利用了主键的自增,即不手动给主键赋值,所以实体类中必须有一个无主键属性的构造方法
       //切记切记切记:在所有的增删改后必须手工提交事务!!!
        sqlSession.commit();

    }

    @Test
    public void testDelete()throws IOException {

        //按id删除数据
        int num = sqlSession.delete("zar.delete",1);
        System.out.println(num);
        //切记切记切记:在所有的增删改后必须手工提交事务!!!
        sqlSession.commit();

    }

    @Test
    public void testUpdate()throws IOException {

        //更新数据
// 配置文件中不需要#{stu.name},直接写变量即可,系统会自动识别stu对象中的该变量的值。而在Java程序中实参也只用写一个,就是对象即可,不用“对象.变量”
        int num = sqlSession.update("zar.update",new Student(3,"hehe","hehe@126.com",30));
        System.out.println(num);
        sqlSession.commit();

    }
}


根据查询的返回值数量选中调用不同的方法,根据SQL语句的类型(增删改查)选中不同的方法。

Mybatis的dtd文件解读:

dtd文件是xml文件的写作规范的规定。在xml文件的头部均有该xml文件的dtd文件的超链接指向。



dtd文件的规则定义:


定义开始标签和结束标签间的内容:


<!ELEMENT 标签名 (当前标签内可定义的内容xxx)>  标签:

dtd文档中的第一个,代表xml中的根标签。

其中:xxx可以定义的内容有:


1、子标签:



如:(标签名?,标签名*,标签名+,标签名,(标签名|标签名)+)。

其中逗号”,“代表子标签的定义顺序;

问号”?“代表子标签可以定义0或1个;

星号”“代表子标签可以定义0或n个;

加号”+“代表子标签可以定义1或n个;

标签名后不加数量符号,代表只能定义唯一一个,且必须定义,括号代表优先级;

竖线”|”代表选择。


2、空标签

用 EMPTY 表示,代表当前标签是一个空标签,不能再定义子标签。如:

mapper标签:

<!--dtd文件中-->

<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>


<!--xml文件中-->

<mappers>
    <mapper resource="StudentMapper.xml"></mapper>
</mappers>


3、#PCDATA:

代表当前标签没有子标签,但是可以定义字符类型的标签体,如:spring中的value标签abc。


注意:dtd文件中的 ELEMENT 标签内定义了标签顺序(前后),在xml文件中使用标签时必须按照顺序填写标签,不然会报错。


定义写在开始标签尖括号内的内容:


<!ATTLIST 标签名 属性名 属性特征 属性名 属性特征> :

标签的属性列表。

属性特征:(可联合使用)


1、CDATA:

字符类型,属性值不能包含空格、回车、换行、制表符。


2、a|b|c:

枚举,代表当前属性的可选值有哪些


3、#IMPLIED:

可选属性


4、#REQUIRED:

必要属性


5、default “数据”:

代表当前属性默认值是什么。


6、NMTOKEN:

字符类型,可以包含空格。代表多个字符串。

<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE | DEFAULT) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED 
>

<!--id是必写的,而返回值类型是可选的-->

<select id="getAll" resultType="student" >
    select id,name,email,age
    from student
</select>

驼峰映射:

驼峰映射,就是将满足sql下划线的标准写法字段自动转换为满足java命名规则的实体属性,不需要用resultMap自己建立映射关系。

如user数据表字段 : u_id , u_name,u_sex;

会自动转换为User类属性匹配的字段:uId,uName,uSex;


在SqlMapConfig.xml文件中:

 <!--
    开启驼峰写法   自动将数据库表字段 s_address 映射为Java属性 sAddress
     <setting name="mapUnderscoreToCamelCase" value="true"/>
    -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

Mapper文件实参、返回值类型别名:

在mybatis中TypeAliasRegistry已经为我们配置的默认别名。

    registerAlias("string", String.class);
 
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
 
    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);
 
    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);
 
    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);
 
    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);
 
    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);
 
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);
 
    registerAlias("ResultSet", ResultSet.class);

如果我们需要实参/返回值Integer类型时,只用输入int即可。

    <delete id="delete" parameterType="int" >
        delete from student where id=#{id}
    </delete>

MyBatis对象分析:

Resources 类

Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析SqlMapConfig.xml文件,返回不同类型的 IO 流对象。

  InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

SqlSessionFactoryBuilder 类

SqlSessionFactory 的创建 , 需要使用 SqlSessionFactoryBuilder 对象的 build() 方法 。 由于SqlSessionFactoryBuilder对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。所以,一般会将该 对象创建为一个方法内的局部对象,方法结束,对象销毁。

  SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

SqlSessionFactory 接口

SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的。创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。

  1. openSession(true):创建一个有自动提交功能的 SqlSession
  2. openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
  3. openSession():同 openSession(false)
  SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

SqlSession 接口

SqlSession  接口对象用于执行持久化操作。一个 SqlSession  对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以 SqlSession 对象的关闭结束。

SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。 SqlSession 在方法内部创建,使用完毕后关闭。

  sqlSession = factory.openSession();

Mybatis中动态代理存在意义:

在三层架构中,业务逻辑层要通过接口访问数据访问层的功能。传统分层模式为了耦合度低,都会用接口指向实现类的对象。这就导致了我们在处理业务的时候需要写 dao 层(数据访问层),然后再写 dao层的实现类,在实现类中通过sqlSeason对象对底层数据库进行访问。而 mybatis 在动态代理之后,在只写 dao层的接口(Maper接口)的情况之下,可以直接指向xml文件,mybatis 就会自动生成 dao的实现类 (Mapper接口),就可以调用 dao的接口了(Mapper实现类)。

并且,在阅读源码之后我们还可以发现,Mybatis的动态代理属于JDK动态代理。

动态代理的实现规范:


(1)UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下。


(2)UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不管。


(3)UsersMapper.xml文件中标签的id值与sMapper.java的接口中方法的名称完全一致。


(4)UsersMapper.xml文件中标签的parameterType属性值与UsersMapper.java的接口中方法的参数类型完全一致。


(5)UsersMapper.xml文件中标签的resultType值与UsersMapper.java的接口中方法的返回值类型完全一致。


(6)UsersMapper.xml文件中namespace属性必须是接口的完全限定名称com.bjpowernode.mapper.UsersMapper。


(7)在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称com.bjpowernode.mapper.UsersMapper。

动态代理情况下mapper.xml文件的注册:

   <!--注册mapper.xml文件-->
    <mappers>

        <!--绝对路径注册-->
        <mapper url="/"></mapper>

        <!--非动态代理方式下的注册(StudentMapper.xml文件在resources文件夹下)-->
        <mapper resource="StudentMapper.xml"></mapper>

        <!--动态代理方式下的单个mapper.xml文件注册-->
        <mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>

        <!--批量注册-->
        <package name="com.bjpowernode.mapper"></package>

    </mappers> 



注意注意注意注意!!!!



完整类名/完全限定名 是从Java文件夹下开始数的而不是src。完全限定名 即 含包名的类名。从src开始的叫类路径!!!!

Mybatis动态代理代码示例:


jdbc.properties:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root


UsersMapper接口:

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Users;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 *  数据访问层的接口,规定的数据库中可进行的各种操作
 */
public interface UsersMapper {
    //查询全部用户信息
    List<Users> getAll();

    //根据用户主键查用户
    Users getById(Integer id);

    //根据用户名模糊查询用户
    List<Users> getByName(String name);

    //优化后的模糊查询
    List<Users> getByNameGood(String name);
    //用户的更新
    int update(Users users);
    //增加用户
    int insert(Users users);
    //根据主键删除用户
    int delete(Integer id);
    //模糊用户名和地址查询
    List<Users> getByNameOrAddress(
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue);
}


UsersMapper.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.bjpowernode.mapper.UsersMapper">

    <!--
      //查询全部用户信息
    List<Users> getAll();
    -->
    <select id="getAll" resultType="users" >
        select id,username,birthday,sex,address
        from users
    </select>

    <!--
      //根据用户主键查用户
    Users getById(Integer id);
    -->
    <select id="getById" parameterType="int" resultType="users">
        select id,username,birthday,sex,address
        from users
        where id=#{zar}
    </select>

    <!--
       //根据用户名模糊查询用户
    List<Users> getByName(String name);
    -->
    <select id="getByName" parameterType="string" resultType="users">
        select id,username,birthday,sex,address
        from users
        where username like '%${zar}%'
    </select>
    <!--
       //用户的更新
    int update(Users users);
    private Integer id;
    private String userName;
    private Date birthday;
    private String sex;
    private String address;
    -->
    <update id="update" parameterType="users" >
        update users set username = #{userName},birthday=#{birthday},sex=#{sex},address=#{address}
        where id=#{id}
    </update>


    <!--
      //增加用户
    int insert(Users users);
    -->
    <insert id="insert" parameterType="users" >
        insert into users (username, birthday, sex, address) values(#{userName},#{birthday},#{sex},#{address})
    </insert>

    <!--
       //根据主键删除用户
    int delete(Integer id);
    -->
    <delete id="delete" parameterType="int" >
        delete from users
        where id=#{id}
    </delete>

</mapper>


SqlMapConfig:

<?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>
    <!--读取jdbc.properties属性-->
    <properties resource="jdbc.properties"></properties>
    <!--设置日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--注册实体类的别名-->
    <typeAliases>
        <package name="com.bjpowernode.pojo"></package>
    </typeAliases>
    <!--配置环境变量-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <!--
                    private String driver;
                    private String url;
                    private String username;
                    private String password;
                -->
                <property name="driver" value="${jdbc.driverClassName}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <!--注册mapper.xml文件-->
    <mappers>
        <!--单个注册-->
        <!--<mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>-->

        <!--批量注册-->
        <!--com.bjpowernode.mapper下的所以xml文件都会被注册-->
        <package name="com.bjpowernode.mapper"></package>
    </mappers>
</configuration>


pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>mybatis_002_users</artifactId>
  <version>1.0</version>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--添加mybatis的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <!--添加mysql的依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.29</version>
    </dependency>
  </dependencies>

  <build>
    <!--指定资源文件的位置-->
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>

      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
    </resources>
  </build>
</project>


Users:

package com.bjpowernode.pojo;

import java.util.Date;

/**
 *
 */
public class Users {
    private Integer id;
    private String userName;
    private Date birthday;
    private String sex;
    private String address;

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Users(String userName, Date birthday, String sex, String address) {
        this.userName = userName;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    public Users(Integer id, String userName, Date birthday, String sex, String address) {
        this.id = id;
        this.userName = userName;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    public Users() {
    }
}


MyTest:

package com.bjpowernode.test;

import com.bjpowernode.mapper.UsersMapper;
import com.bjpowernode.pojo.Users;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;


/**
 *
 */
public class MyTest {

    SqlSession sqlSession;
    //动态代理对象
    UsersMapper uMapper;
    //日期的格式化刷子
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");

    @Before
    public void openSqlSession() throws IOException {
        //1.读取核心配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.取出sqlSession
        sqlSession = factory.openSession(true);//自动提交事务
        //4.取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相对应的标签的功能
        uMapper = sqlSession.getMapper(UsersMapper.class);
    }

    @After
    public void closeSqlSession(){
        sqlSession.close();
    }

    @Test
    public void testGetAll(){

        System.out.println(uMapper.getClass());
        //就是在调用接口的方法,mybatis框架已经为我们把功能代理出来了.
        List<Users> list = uMapper.getAll();
        list.forEach(users -> System.out.println(users));
    }

    @Test
    public void testUpdate() throws ParseException {
        Users u = new Users(7,"haha66",sf.parse("2000-01-01"),"2","北京大兴亦庄66");
        int num = uMapper.update(u);
        System.out.println(num);
        //切记切记切记:手工提交事务
        sqlSession.commit();
    }

    @Test
    public void testById(){
        Users u = uMapper.getById(27);
        System.out.println(u);
    }

    @Test
    public void testGetByName(){
        List<Users> list = uMapper.getByName("小");
        list.forEach(users -> System.out.println(users));
    }

    @Test
    public void testInsert() throws ParseException {
        Users u = new Users("lala66",sf.parse("2001-01-01"),"2","大兴");
        int num = uMapper.insert(u);
        System.out.println(num);
        sqlSession.commit();
        System.out.println(u);
    }

    @Test
    public void testDelete() throws ParseException {
       int num = uMapper.delete(3);
        System.out.println(num);
       // sqlSession.commit();
    }


}

#{}与${}的区别:

1、#{}是

预编译

处理,是占位符,${}是字符串替换,是拼接符。

2、Mybatis在处理#{}的时候会将sql中的#{}替换成 ? 号,调用PreparedStatement来赋值。

3、Mybatis在处理${}的时候就是把${}替换成变量的值,调用Statement来赋值。。


4、#{}的变量替换是在DBMS中、变量替换后,#{}对应的变量自动加上单引号。( 导致了有些地方必须使用${} )

5、${}的变量替换是在DBMS外、变量替换后,${}对应的变量不会加上单引号。


6、使用#{}可以有效的防止sql注入,提高系统的安全性。


7、当实参是对象时,#{}可以读取到对象内部的属性值,而${}不可以。

#{}占位符:


传参大部分都使用 #{} 传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问,可以防止sql注入。


#{}里如何写,看parameterType参数的类型:


(1)如果parameterType的类型是简单类型(8种基本数据类型(包括封装类)+String),则#{}里随便写,类似于形参。

<select id="getById" parameterType="int" resultType="users">  ===>入参类型是简单类型
    select id,username,birthday,sex,address
    from users

    where id=#{zar}  ===>随便写
</select>  


(2)parameterType的类型是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写。

<insert id="insert" parameterType="users" >  ===>入参是实体类
    insert into users (username, birthday, sex, address) values(#{userName},#{birthday},#{sex},#{address})  ==>成员变量名称
</insert>


#{}占位符不能解决的三类问题:

表名不能够用#{} :Select * from #{table}

列名不能够用#{} : select #{column} from table

排序不能够用#{} : select * from table order by #{column}

${}字符串拼接:


字符串拼接,一般用于模糊查询中。建议少用,因为有sql注入的风险。


也分两种情况,同样的看parameterType的类型。

(1)如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只以写value。

<select id="getByName" parameterType="string" resultType="users">  ===>入参是简单类型
    select id,username,birthday,sex,address
    from users
    where username like '%${zar}%'   ===>随便写
</select> 


(2)如果parameterType的类型是实体类的类型,则${}里只能是类中成员变量的名称。(现在已经少用)




当模糊查询时,因为#{}会自动加上单引号,这就导致了会SQL语法错误,所以必须使用${}。即

‘%${zar}%’



但可以使用 concat()函数与#{}进行搭配 从而规避使用${}会导致SQL注入的风险。


而当查询条件的value不是模糊查询时则可以使用#{}。

优化后的模糊查询(以后都要使用这种方式):

<select id="getByNameGood" parameterType="string" resultType="users">
    select id,username,birthday,sex,address
    from users
    where username like concat('%',#{name},'%')
</select>

${}字符串替换:


现有需求:模糊地址或用户名查询。


SQL语句:

  select * from users where username like '%小%';
  select * from users where address like '%市%'


UsersMapper接口:

    //模糊用户名和地址查询
    List<Users> getByNameOrAddress(
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue);


UsersMapper.xml:

    <!--
    //模糊用户名和地址查询
    //如果参数超过一个,则parameterType不写
    List<Users> getByNameOrAddress(
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue);
    -->
    <select id="getByNameOrAddress" resultType="users">
        select id,username,birthday,sex,address
        from users
        where ${columnName} like concat('%',#{columnValue},'%')
    </select>



注意,此处不能用 #{} :

mysql> select username from users where 'id'=1;
Empty set, 1 warning (0.00 sec)        // 查询为空集,因为没有 'id' 字段,只有 id 字段。


// 查询时select、from等语句后面都不可以跟单引号,会导致查询结果有差异,需要使用${},且在这种情况下不会导致sql注入,因为注入后sql语法不正确。而where、like等语句后就可以使用#{}。

@Param注解:

在实际的开发中,经常会遇到多个接口参数的情况。除了将不同的参数封装在一个对象内,还可以使用@param注解的方式传递参数名称。

使用@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以。

使用@Param注解来声明参数的时候,可以不写parameterType。

@注解

简单类型(8种基本数据类型(包括封装类)+String):


dao层示例:

Public User selectUser(@param(“userName”) String name, @param(“userpassword”) Int password);


xml映射对应示例:

<!--使用注解传递参数,这时是不涉及单独一个类型的,所以去掉parameterType属性-->
<select id=" selectUser" resultMap="BaseResultMap">  
    select  *  from user_user_t 
        where user_name = #{userName} and user_password=#{userPassword}  
</select>

注意:采用#{}的方式把@Param注解括号内的参数进行引用(括号内参数对应的是形参如 userName对应的是name),而xml映射中#{}或${}内写的并非是形参的名称而是引用其的@Param()内的名称。

在这里给参数配置@param注解后,Mybatis会自动将参数封装成Map类型,而@param注解的值会成为Map中的key,因此在sql中可以通过配置的注解值来使用参数。


@Param注解JavaBean对象:


dao层示例:

Users test(@Param("users") Users users);


xml映射对应示例:

<select id="test" resultType="users">
    select id,username,birthday,sex,address
    from users
    where username = #{users.userName}
</select>


测试类:

@Test
public void tsetTest() throws ParseException {
    Users users = uMapper.test(new Users("张三",new Date(),"2","随便,因为只取userName"));
}


可以看到,经过@param配置后的参数在xml文件的sql中可以直接使用。但是当参数是JavaBean类型时,使用@param注解后传过来的仅仅只是一个对象,对象内的属性要通过点取值的方式提取出来。



使用@Param注解才需要用“点”的方式取出对象中的属性,如果不使用@Param注解可以直接#{}获取。

插入返回主键值:

业务:当A表更新一条数据(主键id不手动赋值而是采用主键自增的方式让MySQL赋默认值),取B表给A表新增的数据的id的积分增加500分。

分析,这个业务如果采用传统的方式,就得在更新之后再次访问数据库读取新增的数据对应的主键id,增加了数据库的访问次数,性能有所下降。这时我们可以采用Mybatis的<selectKey>标签和 MySQL的select last_insert_id() 方法让新增的主键值返回并赋值给 传来的新增的对象的id属性。

使用 JDBC 方式返回主键自增的值:


Mapper.xml 伪代码:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert语句
</insert>

useGeneratedKeys 设置为true后,MyBatis 会使用 JDBC的getGeneratedKeys 方法来取出由数据库内部生成的主键。获取主键值后将其赋值给 keyProperty 配置的 id 属性。

使用 selectKey 返回主键的值:

有些数据库(如 Oracle)不提供主键自增的功能,而是使用序列得到一个值,然后将这个值赋给 id,再将数据插入数据库。这种情况可以是 <selectKey>标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。


MySQL 数据库对应Mapper.xml 伪代码:

<insert id="insert">
    insert 语句
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>


Oracle 数据库对应 Mapper.xml 伪代码:

<insert id="insert">
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
        SELECT SEQ_ID.nextval from dual
    </selectKey>
    insert 语句
</insert>


<selectKey>标签的参数详解:


keyProperty:users对象的哪个属性来接返回的主键值


resultType:返回的主键的类型


order:在插入语句执行前,还是执行后返回主键的值,有BEFORE和AFTER两个属性。


selectKey标签位置不一样不会影响 selectKey 中的方法 在 insert 前面或者后面执行的顺序,影响执行顺序的是 order 属性。


  • MySQL 是先插入数据才能获取自增主键,所以selectKey在insert后执行,order属性为 AFTER。

  • Oracle 是先从序列中取出主键值然后插入到数据库中,所以 selectKey 在 insert 前执行,order 属性为 BEFORE。

UUID:

当我们的项目需要给一个属性全球唯一的值,那么我们可以采用UUID获得该值。

Java有UUID这个方法,返回一个随机的全球唯一的字符串,一个由36个字母、数字和下划线组成的字符串。

UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString())

System.out.println(uuid.toString().replace("-","").substring(20));
// 转为字符串后可以对其使用一些字符串的函数,可以去除 - ,或截取长度等

MySQL也提供了这样一个方法:

select uuid();

动态sql:

代码复用:


<sql>:用来定义代码片段,可以将所有的列名,或复杂的条件定义为代码片段,供使用时调用。


<include>:用来引用<sql>定义的代码片段。

 <!--定义代码片段-->
    <sql id="allColumns">
        id,username,birthday,sex,address
    </sql>


<!--引用定义好的代码片段-->
    <select id="getAll" resultType="users" >
        select <include refid="allColumns"></include>
        from users
    </select>

动态查询:


根据传过来的对象所持有的属性值对数据库进行属性模糊查询。


<if>:进行条件判断:


test条件判断的取值可以是实体类的成员变量,可以是map的key,可以是@Param注解的名称。


<where>:进行多条件拼接,在查询,删除,更新中使用。


如:

    <!--
      //按指定的条件进行多条件查询
    List<Users> getByCondition(Users users);
    根据实体类中的成员变量是否有值来决定是否添加条件
    private Integer id;  null
    private String userName;  null
    private Date birthday;  null
    private String sex;  null
    private String address;  null
    private int num;  0
    private boolean flag; false
    private double d ;  0.0
    如果是int就要判断是不是0,以此类推。
    -->
<!--当没有条件时底层SQL语句就不会出现where关键字,只有一个条件时会自动把and去掉,大于一个条件时才会在两条件之间加and-->
<!--字符串都要判断null串和空串,但Date类型只用判断null串,不可能为空串-->
    <select id="getByCondition" parameterType="users" resultType="users">
        select <include refid="allColumns"></include>
        from users
        <where>
            <if test="userName != null and userName != ''">
               and username like concat('%',#{userName},'%')
            </if>
            <if test="birthday != null">
               and birthday = #{birthday}
            </if>
            <if test="sex != null and sex != ''">
               and sex = #{sex}
            </if>
            <if test="address != null and address != ''">
                and address like concat('%',#{address},'%')
            </if>
        </where>
    </select>


该代码会根据所传过来的users对象所含的非默认值的属性进行查询,字符串会模糊查询。

动态修改:


如果我想修改id为8的人的name属性:


测试类中:

    @Test
    public void testUpdate() throws ParseException {
        Users u = new Users();
        u.setId("8");
        u.setUserName("hyf");
        uMapper.update(u);
        sqlSession.commit();
    }


UsersMapper.xml文件中:

    <update id="update" parameterType="users" >
        update users set username = #{userName},birthday=#{birthday},sex=#{sex},address=#{address}
        where id=#{id}
    </update>


因为没有给其他属性赋值,所以其他属性是默认值, 而UsersMapper.xml文件中是对所有属性进行修改,当方法执行时,原id为8的数据的除name以外的其他属性都会被赋上默认值,原先的数据就都丢失了。


为了避免这种情况,我们就可以使用<set>标签,只对传来的对象中的非null且非空的数据进行更新。


<set>:有选择的进行更新处理,至少更新一列。

   <!--
       //根据id有选择的更新
    int updateBySet(Users users);
    -->
<!--
    if内的每条更新语句最后(或前面)一定要加上逗号,mybatis会自动删掉最后一个(或第一个)更新属性的逗号从而让SQL语句正确
    update users
    set username = #{userName},birthday=#{birthday},sex=#{sex},address=#{address}
    where id=#{id}
-->
    <update id="updateBySet" parameterType="users">
        update users
        <set>
            <if test="userName != null and userName != ''">
               username = #{userName},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex},
            </if>
            <if test="address != null and address != ''">
                address =#{address} ,
            </if>
        </set>
        where id = #{id}
    </update>


如果除了id之外其他都不手动赋值(也就是说一列都不更新),那么底层的SQL语句中就没了set关键字,即update users where id = #{id} ,很明显就会报错,所以至少要更新一列(一个属性)。(如果一个都不更新那干嘛要调用更新语句?说不过去)

Foreach标签:

用来进行循环遍历,完成批量查询,批量删除,批量增加,批量更新。

批量查询:

我们要查询一次多个id时,在xml文件中SQL语句可以这样写:

select <include refid="allColumns"></include>
from users
where id=2 or id=4 or id=6

而or有一个简单的写法:

select <include refid="allColumns"></include>
from users
where id in (2,4,6)

正是因为有这个特性,我们可以在括号内用foreach语句对数组进行遍历输出从而批量查询多个id。



parameterType超过一个都可以不写,所以数组/集合也可以不写。(其实简单类型也可以不写,即8个基本数据类型、8个包装类以及String)

    <!--
       //查询多个指定id的用户信息
    List<Users> getByIds(Integer []arr);
    -->
<!--
    item是定义foreach所取出来的元素每一个的名称,可任意写,但下面的#{}内的名称需要与item所定义的名称一致。
    separator是每个取出来的元素间的分隔符(如果手动在#{id}前或后加的话会导致多一个逗号而SQL语法错误)
    open="("    是指foreach语句前是一个( ,从而省略foreach前的( ,这样可以让xml文件中跟整齐一些
    close=")"   是指foreach语句前是一个) ,从而省略foreach前的) ,这样可以让xml文件中跟整齐一些

-->
    <select id="getByIds" resultType="users">
        select <include refid="allColumns"></include>
        from users
        where id in
           <foreach collection="array" item="id" separator="," open="(" close=")">
               #{id}
           </foreach>
    </select>


<foreach>参数详解:

collection:用来指定入参的类型,如果是List集合,则为list,如果是Map集合,则为map,如果是数组,则为array。

item:每次循环遍历出来的值或对象的名称(局部变量名,可任意,但下面的#{}内的名称需要与item所定义的名称一致。)。(如果取出来的是对象,那么需要通过 “对象.变量” 的形式取出值)

separator:多个值或对象或语句之间的分隔符。

open:foreach:代替循环外面的前括号(使得原先循环外的括号可以省略)(不可替代循环内的括号) 。

close:foreach:代替循环外面的后括号(使得原先循环外的括号可以省略)(不可替代循环内的括号) 。

批量删除:

同批量查询。

    <!--
      //批量删除
    int deleteBatch(Integer []arr);
    -->
    <delete id="deleteBatch" >
        delete from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

批量增加:

    <!--
      //批量增加
    int insertBatch(List<Users> list);
        private Integer id;  null
        private String userName;  null
        private Date birthday;  null
        private String sex;  null
        private String address;  null
    -->
    <insert id="insertBatch">
        insert into users(username, birthday, sex, address) values
        <foreach collection="list" item="u" separator="," >
            (#{u.userName},#{u.birthday},#{u.sex},#{u.address})
        </foreach>
    </insert>

批量修改:



注意:要使用批量更新,必须在


jdbc.properties




属性文件中的


url




中添加


&allowMultiQueries=true




,允许多行操作。


之前的是一个SQL语句影响多行(插入),但批量更新是多条SQL语句影响多行,所以要开启。

<!--    有选择的批量更新,至少更新一列-->

    <update id="updateSet"  >
       <foreach collection="list" item="u" separator=";">

        update users

        <set>

            <if test="u.userName != null  and u.userName != ''">
                username=#{u.userName},
            </if>

            <if test="u.birthday != null">
                birthday = #{u.birthday},
            </if>

            <if test="u.sex != null  and u.sex != ''">
                sex = #{u.sex},
            </if>

            <if test="u.address != null  and u.address != ''">
                address = #{u.address}
            </if>

        </set>

        where id = #{u.id}

       </foreach>
    </update>


foreach对整一条更新语句进行重复,且每一条更新语句中都进行动态SQL确定所要更改的属性。

@Test

public void testUpdateBatch()throws Exception{
    List<Users> list = new ArrayList<>();
    Users u1 = new Users(3,"张1167",new SimpleDateFormat("yyyy-MM-dd").parse("1997-02-03"),"2","北京大兴亦庄1111");
    Users u2 = new Users(4,"李2267",new SimpleDateFormat("yyyy-MM-dd").parse("1998-02-03"),"1","北京大兴亦庄2333");
    Users u3 = new Users(5,"王3367",new SimpleDateFormat("yyyy-MM-dd").parse("1999-02-03"),"2","北京大兴亦庄122");
    list.add(u1);
    list.add(u2);
    list.add(u3);

    int num = mapper.updateSet(list);
    session.commit();
    System.out.println(num);

}

指定参数位置:


如果入参是多个,除了使用@param注解之外,还可以使用指定参数位置,参数位置下标从0开始,3.4.1版本之前可以直接写 #{参数下标} ,3.4.1版本之后必须写 #{arg参数下标} 。

例如:查询指定日期范围内的用户信息。

    <!--
      //查询指定日期范围内的用户
    List<Users> getByBirthday(Date begin, Date end);
    -->

    <select id="getByBirthday" resultType="users">
        select <include refid="allColumns"></include>
        from users
        where birthday between #{arg0} and #{arg1}
    </select>   

入参是map(重点掌握) :


如果实参中有数量的起始和终止、价格的起始和终止、日期的起始和终止等,继续用指定参数位置的方式的话,下标全是数字,没有明确的语义,可读性很差。而如果使用@param则每一个实参都要定义一下,很麻烦。


所以如果入参超过一个以上,建议使用map封装查询条件,更有语义,查询条件更明确。


UsersMapper接口:

  List<Users> getByMap(Map map);


UsersMapper.xml文件:

    <!--
       //入参是map
    #{birthdayBegin} 对应的就是map中的key,取出来的就是对应的值value。
    -->
    <select id="getByMap" resultType="users" >
        select <include refid="allColumns"></include>
        from users
        where birthday between #{birthdayBegin} and #{birthdayEnd}
    </select> 


测试类中:

    @Test
    public void testGetByMap() throws ParseException {
        Date begin = sf.parse("1999-01-01");
        Date end = sf.parse("1999-12-31");
        Map map = new HashMap<>();
        map.put("birthdayBegin",begin);
        map.put("birthdayEnd", end);
        List<Users> list = uMapper.getByMap(map);
        list.forEach(users -> System.out.println(users));
    }

返回值为一行map:

如果返回的数据实体类无法包含,可以使用map返回多张表中的若干数据。

返回的map的key就是列名(SQL查询时列名起别名则别名是key),对应的value是属性的值。每一个属性就是一个key,该key对应的value就是该属性的值。


业务:根据id属性查询username属性和address属性。


UsersMapper接口:

    //返回值是map(一行)
    Map getReturnMap(Integer id);


UsersMapper.xml文件:

    <!--
       //返回值是map(一行)
    Map getReturnMap(Integer id);
    -->
    <select id="getReturnMap" parameterType="int" resultType="map">
        select username nam,address a
        from users
        where id=#{id}
    </select>


测试类:

    @Test
    public void testReturnMapOne(){
        Map map = uMapper.getReturnMap(5);
        System.out.println(map);
        System.out.println(map.size());
    }

返回值为多行map:


若在返回值为一行map的基础之上,查询的值不止一个。可以用一个List集合把Map集合装起来。


每一个属性就是一个key,该key对应的value就是该属性的值。每一个Map就是一行。


把map当成一个对象装到list里面,一个map含一行数据(usersname+address)。


UsersMapper接口:

    //返回多行的map
    List<Map> getMulMap();


UsersMapper.xml文件:

    <!--
      //返回多行的map
    List<Map> getMulMap();
    -->
    <select id="getMulMap" resultType="map">
        select username,address
        from users
    </select>

resultType为map,因为返回的最下单位为map,如果为list,那么数据会被打乱。


测试类:

    @Test
    public void testReturnMapMul(){
        List<Map> list = uMapper.getMulMap();
        list.forEach(map -> System.out.println(map));
    }

列名和类中成员变量名称不一致:


数据库:




BookMapper接口:

    public interface BookMapper {
        //查询全部图书
        List<Book> getAll();
    }


BookMapper.xml文件:

    <select id="getAll" resultType="book">
        select bookid,bookname
        from book
    </select>


Book类:




测试类:

package com.bjpowernode.test;

import com.bjpowernode.mapper.BookMapper;
import com.bjpowernode.mapper.CustomerMapper;
import com.bjpowernode.mapper.OrdersMapper;
import com.bjpowernode.pojo.Book;
import com.bjpowernode.pojo.Customer;
import com.bjpowernode.pojo.Orders;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;
    BookMapper bookMapper;


    @Before  //在所有的@Test方法执行前先执行的代码
    public void openSqlSession() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();
        bookMapper = sqlSession.getMapper(BookMapper.class);

    }

    @After
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testGetAll(){
        List<Book> list = bookMapper.getAll();
        list.forEach(book -> System.out.println(book));
    }

}


很明显,列名跟实体类的成员变量名称不一致。(xml文件中须采用列名作为查询名,因为xml文件直接就对应了底层的SQL语句)


运行结果:


当列名跟实体类的成员变量名称不一致时,程序无法直接获得数据库的值,这个过程不会报错,但取不出来任何值,因为缺少列名与实体类的成员变量之间的一个映射关系。

使用别名进行成员变量和列名的映射:

只需在原始案例的基础之上,为查询的列名起别名即可,别名须跟实体类中成员变量的名称一致。

    <select id="getAll" resultType="book">
        select bookid id,bookname name
        from book
    </select>

使用resultMap进行成员变量和列名的映射:

    <!--
        使用resultMap手工完成映射
        type的值就是所要返回的实体类的名称,就是返回值(一个对象)的类型。
    -->
    <resultMap id="bookmap" type="book">
        <!--主键绑定-->
        <id property="id" column="bookid"></id>
        <!--非主键绑定-->
        <result property="name" column="bookname"></result>
    </resultMap>
    <select id="getAll" resultMap="bookmap">
        select bookid,bookname
        from book
    </select>


property表示类中的变量名,column表示数据库中列名,type表示所要返回的实体类名称。


表之间的关联关系:


关联关系是有方向的。


(1)一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方,就是一对多关联。


(2)多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生方,就是多对一关联。


(3)一对一关联:一个老师辅导一个学生,一个学生只请教一个老师。学生和老师是一对一。


(4)多对多关联:园区划线的车位和园区的每一辆车。任意一个车位可以停任意一辆车。任意一车辆车可以停在任意一个车位上。

一对多关联关系:

客户和订单就是典型的一对多关联关系。一个客户名下可以有多个订单。

使用一对多的关联关系,可以满足查询客户的同时查询该客户名下的所有订单。


数据:

use ssm;

Create table customer(
id int primary key auto_increment,
name varchar(32),
age int
);
insert into customer values(1,'张三',22);
insert into customer values(2,'李四',23);
insert into customer values(3,'王五',24);

Create table orders(
id int primary key auto_increment,
orderNumber varchar(16),orderPrice double,
customer_id int ,
foreign key (customer_id) references customer(id)
);
insert into orders values(11,20,22.22,1);
insert into orders values(12,60,16.66,1);
insert into orders values(13,90,19.99,2);

select * from customer;
select * from orders;


Customer实体类:

package com.bjpowernode.pojo;

import java.util.List;

/**
 *
 */
public class Customer {

    //customer表中的三个列
    private Integer id;
    private String name;
    private Integer age;

    // 用一个list集合装该客户名下的所有订单
    private List<Orders> ordersList;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", ordersList=" + ordersList +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<Orders> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Orders> ordersList) {
        this.ordersList = ordersList;
    }

    public Customer(Integer id, String name, Integer age, List<Orders> ordersList) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.ordersList = ordersList;
    }

    public Customer() {
    }
}


Orders实体类:

package com.bjpowernode.pojo;

/**
 *
 */
public class Orders {
    private Integer id;
    private String orderNumber;
    private Double orderPrice;


    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", orderNumber='" + orderNumber + '\'' +
                ", orderPrice=" + orderPrice +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Double getOrderPrice() {
        return orderPrice;
    }

    public void setOrderPrice(Double orderPrice) {
        this.orderPrice = orderPrice;
    }

    public Orders(Integer id, String orderNumber, Double orderPrice) {
        this.id = id;
        this.orderNumber = orderNumber;
        this.orderPrice = orderPrice;
    }

    public Orders() {
    }
}


CustomerMapper接口:

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Customer;

/**
 *
 */
public interface CustomerMapper {

    //根据客户的id查询客户所有信息并同时查询该客户名下的所有订单
    Customer getById(Integer id);
}


CustomerMapper.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.bjpowernode.mapper.CustomerMapper">
   <!--
     //根据客户的id查询客户所有信息并同时查询该客户名下的所有订单
    Customer getById(Integer id)

    实体类:
    //customer表中的三个列
    private Integer id;
    private String name;
    private Integer age;

    //该客户名下的所有订单的集合
    private List<Orders> ordersList;
   -->

    <resultMap id="customermap" type="customer">
        <!--主键绑定-->
        <id property="id" column="cid"></id>
        <!--非主键绑定-->
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <!--多出来的一咕噜绑定ordersList
        Orders实体类:
        private Integer id;
        private String orderNumber;
        private Double orderPrice;
        -->
        <!--collection是集合,表示把数据装进集合内,该标签下定义集合中成员变量与列名之间的映射关系-->
        <collection property="ordersList" ofType="orders">
            <!--主键绑定-->
            <id property="id" column="oid"></id>
            <!--非主键绑定-->
            <result property="orderNumber" column="orderNumber"></result>
            <result property="orderPrice" column="orderPrice"></result>

        </collection>
    </resultMap>

    <select id="getById" parameterType="int" resultMap="customermap">
        select c.id cid,name,age,o.id oid,orderNumber,orderPrice,customer_id
        from customer c left  join orders o on c.id = o.customer_id
        where c.id=#{id}
    </select>

</mapper>


property表示类中的变量名,column表示数据库中列名,type表示所要返回的实体类名称。


<collection property=”pojo的集合属性” ofType=”集合中的pojo对象”>


<association property=”对象名” javaType=”对象的类型”>


association 一对一或多对一

collection 一对多

<!--column不做限制,可以为任意表的字段,而property须为type 定义的pojo属性-->
<resultMap id="唯一的标识" type="映射的pojo对象">
  <id column="表的主键字段,或者可以为查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
  <result column="表的一个字段(可以为任意表的一个字段)" jdbcType="字段类型" property="映射到pojo对象的一个属性(须为type定义的pojo对象中的一个属性)"/>
  <association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
    <id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的主席属性"/>
    <result  column="任意表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
  </association>
  <!-- 集合中的property须为oftype定义的pojo对象的属性-->
  <collection property="pojo的集合属性" ofType="集合中的pojo对象">
    <id column="集合中pojo对象对应的表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
    <result column="可以为任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />  
  </collection>
</resultMap>



必须是左(外)连接,因为我主要的目的是查询用户,其次才是显示查询出来的用户的订单,如果用内连接,那么会导致没有订单的用户无法显示。



如果起了别名,那么SQL查询出来的列名就变成了别名,此时column需要赋值为别名。



SQL查询中,customer_id可以写,查出来可以不设置映射接收,没什么影响,如果不写的话,以后扩展困难。


测试类:

package com.bjpowernode.test;

import com.bjpowernode.mapper.BookMapper;
import com.bjpowernode.mapper.CustomerMapper;
import com.bjpowernode.mapper.OrdersMapper;
import com.bjpowernode.pojo.Book;
import com.bjpowernode.pojo.Customer;
import com.bjpowernode.pojo.Orders;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;
    CustomerMapper customerMapper;


    @Before  //在所有的@Test方法执行前先执行的代码
    public void openSqlSession() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();

        customerMapper = sqlSession.getMapper(CustomerMapper.class);
    }

    @After
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testGetCustomerById(){
        Customer customer = customerMapper.getById(3);
        System.out.println(customer);

    }

}

针对一对多查询的改进:

这里调用本类的其他查询方法,实现代码的复用。


column标签就是找到表中指定的那一列并接收他的值,然后让其他关键字对该值进行操作。


如果collection标签是使用嵌套查询,格式如下:

<collection column="传递给嵌套查询语句的字段参数" property="pojo对象中集合属性" ofType="集合属性中的pojo对象" select="嵌套的查询语句" > 

</collection>


注意:标签中的column:要传递给select查询语句的参数,如果传递多个参数,格式为column= “{参数名1=表字段1,参数名2=表字段2} ”;

但这种方式还是有缺陷,因为我们在实际开发中,希望的是CustomerMapper.xml中全是返回Customer对象信息的SQL语句,OrdersMapper.xml中全是返回Orders对象信息的SQL语句,而这种方式就会导致一个CustomerMapper.xml文件中有返回Orders对象信息的SQL语句,不好维护。


再改进:




Collection标签的select语句有改动。


将原来本Mapper的方法调用改为了跨Mapper的方法调用,CustomerMapper.xml调用OrdersMapper.xml文件中返回Orders对象信息的SQL语句。

多对一关联关系:

订单和客户就是多对一关联,站在订单的方向查询订单的同时将客户信息查出。


数据:


同一对多。


orders实体类:

package com.bjpowernode.pojo;

/**
 *
 */
public class Orders {
    private Integer id;
    private String orderNumber;
    private Double orderPrice;

    //关联下此订单的客户信息,多方持有一方的对象
    private Customer customer;

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", orderNumber='" + orderNumber + '\'' +
                ", orderPrice=" + orderPrice +
                ", customer=" + customer +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Double getOrderPrice() {
        return orderPrice;
    }

    public void setOrderPrice(Double orderPrice) {
        this.orderPrice = orderPrice;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Orders(Integer id, String orderNumber, Double orderPrice, Customer customer) {
        this.id = id;
        this.orderNumber = orderNumber;
        this.orderPrice = orderPrice;
        this.customer = customer;
    }

    public Orders() {
    }
}



一个客户可能对应多条订单,所以如果站在客户一方,那么就是一对多,那么客户类中需要有一个集合来装该客户的所有订单信息。



而一条订单仅仅只会对应一个客户,所以如果站在订单一方,那么就是多对一,那么订单类中需要有一个客户类的对象来装客户信息即可。(不需要集合,因为一条订单就只对应一个客户)


Customer实体类:

package com.bjpowernode.pojo;

import java.util.List;

/**
 *
 */
public class Customer {

    //customer表中的三个列
    private Integer id;
    private String name;
    private Integer age;

    //该客户名下的所有订单的集合,一方持有多方的集合
    private List<Orders> ordersList;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", ordersList=" + ordersList +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<Orders> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Orders> ordersList) {
        this.ordersList = ordersList;
    }

    public Customer(Integer id, String name, Integer age, List<Orders> ordersList) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.ordersList = ordersList;
    }

    public Customer() {
    }
}


OrdersMapper接口:

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Orders;

/**
 *
 */
public interface OrdersMapper {
    //根据主键查询订单,并同时查询下此订单的客户信息
    Orders getById(Integer id);
}


OrdersMapper.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.bjpowernode.mapper.OrdersMapper">
    <!--
      //根据主键查询订单,并同时查询下此订单的客户信息
    Orders getById(Integer id);
    -->

    <!--
      手工绑定数据
      实体类
        private Integer id;
        private String orderNumber;
        private Double orderPrice;

        //关联下此订单的客户信息,多方持有一方的对象
        private Customer customer;
    -->
    <resultMap id="ordersmap" type="orders">
        <!--主键绑定-->
        <id property="id" column="oid"></id>
        <!--非主键绑定-->
        <result property="orderNumber" column="orderNumber"></result>
        <result property="orderPrice" column="orderPrice"></result>
        <!--多出来的一咕噜绑定
            private Integer id;
            private String name;
            private Integer age;

            //该客户名下的所有订单的集合,一方持有多方的集合
            private List<Orders> ordersList; //不用管

        <association property="对象名" javaType="对象的类型">            
        -->
        <association property="customer" javaType="customer">
            <id property="id" column="cid"></id>
            <result property="name" column="name"></result>
            <result property="age" column="age"></result>

        </association>
    </resultMap>
    <select id="getById" parameterType="int" resultMap="ordersmap">
        select o.id oid,orderNumber,orderPrice,customer_id,c.id cid,name,age
        from orders o inner join customer c on o.customer_id = c.id
        where o.id=#{id}
    </select>
</mapper>



内连接即可,因为不会出现没有客服的订单。



association和collection如果有需求的话是可以嵌套使用的。


测试类:

package com.bjpowernode.test;

import com.bjpowernode.mapper.BookMapper;
import com.bjpowernode.mapper.CustomerMapper;
import com.bjpowernode.mapper.OrdersMapper;
import com.bjpowernode.pojo.Book;
import com.bjpowernode.pojo.Customer;
import com.bjpowernode.pojo.Orders;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;
    OrdersMapper ordersMapper;


    @Before  //在所有的@Test方法执行前先执行的代码
    public void openSqlSession() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();

        ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    }

    @After
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testGetOrdersById(){
        Orders orders = ordersMapper.getById(11);
        System.out.println(orders);

    }
}

针对多对一查询的改进:


第一次改进:


缺点同一对多。


第二次改进:




一对一关联:




数据:

USE mybatis;

-- ----------------------------
-- Table structure for `tb_head_teacher`
-- ----------------------------
#DROP TABLE IF EXISTS `teacher`;
create table teacher(
    tid int primary key auto_increment,
    tname varchar(20),
    tage int
)DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of teacher
-- ----------------------------
insert into teacher(tname,tage) values('ZhangSan',40);

#DROP TABLE IF EXISTS `classes`;
create table classes( 
cid int primary key auto_increment, 
cname varchar(20), 
ctid int unique, 
 foreign key(ctid) references teacher(tid)
)DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orders
-- ----------------------------
insert into classes(cname,ctid) values('Class One',1);

select * from teacher;
select * from classes;


Teacher实体类:

package com.bjpowernode.pojo;


public class Teacher {
    private Integer id;
    private String name;
    private Integer age;

    public Teacher() {

    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Teacher(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}


Classes实体类:

package com.bjpowernode.pojo;

public class Classes {
    private Integer id;
    private String name;

    private Teacher teacher;

    @Override
    public String toString() {
        return "Classes{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    public Classes(Integer id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }

    public Classes() {
    }
}


CATMapper接口:

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Classes;

/**
 *
 */
public interface CATMapper {
    Classes getCLassAndTeacher(Integer id);
}


CATMapper.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.bjpowernode.mapper.CATMapper">
<!--    Classes getCLassAndTeacher(Integer id);

Classes实体类
    private Integer id;
    private String name;

    private Teacher teacher;

Teacher实体类:
    private Integer id;
    private String name;
    private Integer age;

-->

    <resultMap id="ClassesMap" type="Classes">
        <id property="id" column="cid"></id>
        <result property="name" column="cname"></result>
        
        <association property="teacher" javaType="teacher">
            <id property="id" column="tid"></id>
            <result property="name" column="tname"></result>
            <result property="age" column="tage"></result>
        </association>
    </resultMap>
    <select id="getCLassAndTeacher" parameterType="int" resultMap="ClassesMap">
        select c.cid,c.cname,c.ctid,t.tid,t.tname,t.tage
        from classes c inner join teacher t on c.ctid=t.tid
        where c.cid=1
    </select>

</mapper>


测试类:

package com.bjpowernode.test;

import com.bjpowernode.mapper.BookMapper;
import com.bjpowernode.mapper.CustomerMapper;
import com.bjpowernode.mapper.OrdersMapper;
import com.bjpowernode.mapper.CATMapper;
import com.bjpowernode.pojo.Book;
import com.bjpowernode.pojo.Classes;
import com.bjpowernode.pojo.Customer;
import com.bjpowernode.pojo.Orders;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;
    CATMapper catMapper;


    @Before  //在所有的@Test方法执行前先执行的代码
    public void openSqlSession() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();

        catMapper= sqlSession.getMapper(CATMapper.class);
    }

    @After
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testTeacherAndClass(){
        Classes classes = catMapper.getCLassAndTeacher(1);
        System.out.println(classes);
    }
}

多对多关联:

多对多关联中,需要通过中间表化解关联关系。中间表描述两张主键表的关联。中间表没有对应的实体类。Mapper.xml文件中也没有中间表的对应标签描述,只是在查询语句中使用中间表来进行关联。

如:老师与所教的班级就是一个多对多的关系,一个老师教多个班,一个班又有多个老师。


创建t_classes




创建t_classessTeacher




创建t_teacher


数据:

/*如果DB不存在,不会报错*/
#DROP DATABASE IF EXISTS many;

#SET FOREIGN_KEY_CHECKS=0;
CREATE DATABASE many DEFAULT CHARSET utf8;

use many;
drop table if exists t_classes;
drop table if exists t_classessTeacher;
drop table if exists t_teacher;

create table t_classes
(
    cid int primary key auto_increment,
    cname varchar(20)                                     
);

create table t_teacher
(
    tid int primary key auto_increment,
    tname varchar(20)
);

create table t_classessTeacher
(
    cid int,
    tid int,
    constraint fk_bid foreign key(cid) references t_classes(cid),
    constraint fk_cid foreign key(tid) references t_teacher(tid)
);

insert into t_teacher values (default,'王老师');
insert into t_teacher values (default,'李老师');
insert into t_teacher values (default,'张老师');

insert into t_classes values (default,'大一');
insert into t_classes values (default,'大二');
insert into t_classes values (default,'大三');

insert into t_classessTeacher values (1,1);
insert into t_classessTeacher values (2,1);
insert into t_classessTeacher values (1,3);
insert into t_classessTeacher values (2,3);

select * from t_classes;
select * from t_teacher;
select * from t_classessTeacher;


Classes实体类:

package com.bjpowernode.pojo;
import java.util.List;

public class Classes {
    private Integer cid;
    private String cname;
    private List<Teacher> teachers;
    public Integer getCid() {
        return cid;
    }
    public void setCid(Integer cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public List<Teacher> getTeachers() {
        return teachers;
    }
    public void setTeachers(List<Teacher> teachers) {
        this.teachers = teachers;
    }
    @Override
    public String toString() {
        return "Classes [cid=" + cid + ", cname=" + cname + ", teachers=" + teachers + "]";
    }
}


Teacher实体类:

package com.bjpowernode.pojo;
import java.util.List;

public class Teacher {
    private Integer tid;
    private String tname;
    private List<Classes> classes;
    public Integer getTid() {
        return tid;
    }
    public void setTid(Integer tid) {
        this.tid = tid;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }
    public List<Classes> getClasses() {
        return classes;
    }
    public void setClasses(List<Classes> classes) {
        this.classes = classes;
    }
    @Override
    public String toString() {
        return "Teacher [tid=" + tid + ", tname=" + tname + "]";
    }
}


ClassesMapper接口:

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Classes;

/**
 *
 */
public interface ClassesMapper {

    //根据客户的id查询客户所有信息并同时查询该客户名下的所有订单
    Classes findClassesWithTeacher(Integer id);
}


ClassesMapper.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.bjpowernode.mapper.ClassesMapper">

    <!-- 自定义结果映射 -->
    <resultMap id="ClassesWithTeacherResult" type="Classes">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!-- 多表关联映射 -->
        <collection property="teachers" ofType="Teacher">
            <id property="tid" column="tid"/>
            <result property="tname" column="tname"/>
        </collection>
    </resultMap>
    <select id="findClassesWithTeacher" parameterType="Integer" resultMap="ClassesWithTeacherResult">
        select c.cid,c.cname,t.tid,t.tname,ct.cid,ct.tid from t_classes c
            inner join t_classessTeacher ct on ct.cid=c.cid
            inner join t_teacher t on ct.tid=t.tid
        where c.cid=#{cid}
    </select>

<!--

SQL92语法:
    select * from t_classes c,t_teacher t,t_classessTeacher ct
    where ct.cid=c.cid
    and ct.tid=t.tid
    and c.cid=#{cid}
-->


</mapper>


测试类:

package com.bjpowernode.test;

import com.bjpowernode.mapper.*;
import com.bjpowernode.pojo.Classes;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

/**
 *
 */
public class MyTest {
    SqlSession sqlSession;
    ClassesMapper classesMapper;

    @Before  //在所有的@Test方法执行前先执行的代码
    public void openSqlSession() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出sqlSession的对象
        sqlSession = factory.openSession();
        classesMapper = sqlSession.getMapper(ClassesMapper.class);

    }

    @After
    public void closeSqlSession(){
        //关闭sqlSession
        sqlSession.close();
    }


    @Test
    public void testfindClassesWithTeacher(){
        Classes classes = classesMapper.findClassesWithTeacher(1);
        System.out.println(classes);
    }
}

事务:

事务有四个特性:一致性,持久性,原子性,隔离性。


在MyBatis框架中设置事务:

  <transactionManager type="JDBC"></transactionManager>  ===>程序员自己控制处理的提交和回滚


也可另外设置为自动提交:

  sqlSession = factory.openSession();  ===>默认是手工提交事务,设置为false也是手工提交事务,如果设置为true,则为自动提交.
  sqlSession = factory.openSession(true);  ===>设置为自动提交,在增删改后不需要commit();


缓存:

MyBatis框架提供两级缓存,一级缓存和二级缓存。默认开启一级缓存。缓存就是为了提交查询效率.


使用缓存后,查询的流程:

查询时先到缓存里查,如果没有则查询数据库,放缓存一份,再返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库。如果数据库中发生commit()操作,则清空缓存。(缓存存在内存中,数据库存在硬盘中,对内存的读取操作要远远快于对硬盘的读取操作)


一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据。


二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域。

代码验证一级缓存:

    @Test
    public void testCache() {
        //第一次取id=5的用户
        Users u1 = uMapper.getById(5);
        System.out.println("第一次取出的用户u1:"+u1);
        System.out.println("***********************************");

        Users u2 = uMapper.getById(5);
        System.out.println("第二次取出的用户u2:"+u2);
        System.out.println(u1 == u2);
    }


如果数据库中发生commit()操作,则清空缓存:

    @Test
    public void testCache() throws ParseException {
        //第一次取id=5的用户
        Users u1 = uMapper.getById(5);
        System.out.println("第一次取出的用户u1:"+u1);
        System.out.println("***********************************");

        //执行更新操作
        Users u = new Users(27,"djdj",sf.parse("2009-01-01"),"2","北京大兴亦庄djdj");
        int num = uMapper.update(u);
        sqlSession.commit();

        Users u2 = uMapper.getById(5);
        System.out.println("第一次取出的用户u1:"+u2);
        System.out.println(u1 == u2);
    }



ORM:

ORM(Object Relational Mapping):对象关系映射。

MyBatis框架是ORM非常优秀的框架。

java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换的对应关系称为映射。整个这套操作就是ORM。

持久化的操作:将对象保存到关系型数据库中 ,或是将关系型数据库中的数据读取出来以对象的形式封装,两种过程都叫做数据的持久化。

MyBatis是持久化层优秀的框架。



版权声明:本文为hyf24原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。