Java EE核心框架实战(第2版)

978-7-115-46361-6
作者: 高洪岩
译者:
编辑: 傅道坤
分类: Java

图书目录:

详情

本书基于MyBatis3+Hibernate5+Struts2+Spring4MVC+Spring4,融合了作者多年软件开发和培训的经验,面向实战,通过丰富且有参考价值的案例,展现了JavaEE开发领域中最核心、最实用的开发技术,帮助读者全面提升开发技能。

图书摘要

版权信息

书名:Java EE核心框架实战(第2版)

ISBN:978-7-115-46361-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


• 著    高洪岩

  责任编辑 傅道坤

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


本书的宗旨是提高读者学习Java EE的效率,增强其项目实战能力。为此,本书摒弃了软件公司中不常用或不实用的技术,而是采用近200个开发案例,为读者讲解了开发商业软件的必备知识,帮助读者进行“精要”式的学习,汲取Java EE的思想,正确地进行项目实战。

本书涵盖了MyBatis 3、Struts 2、Ajax、JSON、jQuery、Spring 4 MVC、Hibernate 5、Spring 4等主流Java EE框架的核心开发技术,介绍了MVC框架的原理实现、上传、下载、数据验证、国际化、多模块分组开发、转发/重定向、JSON的解析;将Ajax及JSON和MVC框架进行整合开发;ORM框架的CURD操作以及MyBatis和Hibernate中的映射文件使用。本书还使用大量篇幅介绍了Spring 4中的核心技术DI与AOP,以及企业中常用框架的整合开发,框架包含Struts 2、Spring 4 MVC、MyBatis 3、Hibernate 5、Spring 4整合开发等内容。

本书语言简洁,示例丰富,可帮助读者迅速掌握使用主流开源Java EE框架进行开发所需的各种技能。本书适合具有一定Java编程基础的读者,以及使用Java进行软件开发、测试的从业人员阅读。


感谢读者的厚爱。本书第1版在市场上反响强烈,很多读者向作者提出了若干建设性的意见,同时由于IT技术发展飞快,需要将这些技术进行更新;本书的第2版本加入了常用框架最新版本的新技术,完善了每个框架的学习案例,目的是让读者学习到更多的知识,这也是作者写作的初衷。

作者多年从事软件开发相关的工作,但从近几年用人单位对人才招聘的要求来看,越来越趋向于“实战性”,也就是要求员工进入软件公司后能立即融入开发的任务中,快速地为软件公司创造巨大的经济利益。

我的很多学员建议能否出一本内容精悍而不失实用价值的主流Java EE开源框架图书,使其只包含主流框架最重要、最核心、最常用的内容。这样读者就可以尽快上手,并沿着这个核心指导出一些方向,自行在工作和学习中不断拓展和深掘,这就是我写这本书的主要目的。

的确,他们的建议非常有道理的,因为Java EE世界非常庞大,以至于世界上没有任何一本书能把它讲得巨细靡遗。要想学好Java语言或Java EE框架并吸取其中丰富的编码技巧、设计模式、代码优化,将他们熟练地综合应用在软件项目中,并没有捷径。所以,就有了本书。本书不但涵盖了学习主流Java EE框架所须掌握的核心技术,也涵盖了使用它们进行项目实战的必备知识。本书的主旨就是希望让读者尽快掌握开源Java EE框架的核心内容,正确进行项目实战,汲取Java EE的思想,并最终将这种思想活用到实际工作中。

现在主流的Java EE框架还是MyBatis、Hibernate、Struts 2、Spring MVC、Spring,让初入Java EE框架的学习者从零开始到最终掌握这几个框架,一直是我的写作目标。有些Java EE开源的框架的确能极大改善开发效率,但由于使用的人不多,所以覆盖面比较窄,而软件公司在招聘时的技术需求大多数情况下却是“大众化”的,这就要求应聘者在面试前就有主流Java EE框架的学习或使用经验。如果读者找不到合适的教材,导致在学习某一项技术时根本摸不清哪些知识点是常用的、哪些是不常用的,就会大大降低学习者的学习效率,分散大量宝贵的注意力。

首先,本书适合所有Java的程序员阅读。Java EE作为Java开源世界的主流框架,Java程序员没有理由不学习它们。其次,本书适合那些希望学习Java EE框架编程的在校学生。由于学校的功课很多,而一本大部头的框架书籍需要花费大量的时间去研读。在学习效率上,本书可以快速带领他们进入Java EE框架开发的殿堂,同时又不会遗漏掉应该掌握的核心技能。

第1章将会介绍基于SQL映射的MyBatis框架,可以使用此框架操作主流的数据库,并介绍MyBatis核心API的使用,采用自定义封装法来简化MyBatis的操作代码,进而加快开发效率。

第2章主要讲解了MyBatis映射有关的知识,包括<sql>、<resultMap>、<choose>、<set>、<foreach>等常用标签;DB连接信息存储到Properties文件的读取;使用JDBC数据源;别名typeAliases的配置;CLOB字段的读取以及分页等必备知识。

第3章在开始就为读者介绍了一个微型MVC框架的设计,体会MVC框架的工作原理;并介绍了基于Struts 2的有刷新验证及文本信息的国际化,转发/重定向的操作,多模块分组开发的实现,使用松/紧耦版的API进行实用开发。

第4章讲解了如何使用Struts 2框架实现上传、下载(支持中文)的功能,并且支持多文件上传。

在第5章中,读者将学习在Web开发中的主流技术Ajax,还要学习JSON技术,并将JSON技术结合Ajax和Struts 2实现前台和后台数据通信的功能,掌握不同格式JSON的解析技术。Ajax是学习Java Web必须要走的路。

第6、7、8章详细介绍主流ORM框架Hibernate 4的使用,包括核心API的使用和HQL语言的使用。

第9章介绍Java EE中的JPA规范,现主流的ORM框架都支持JPA,比如Hibernate和OpenJPA等,所以掌握JPA也是考验一个程序员对Java EE的使用程度的一个标准。

第10章开始介绍Spring 4中的IOC和AOP技术,深入了解动态代理的实现与原理。

第11章又以Struts 2+Hibernate 5+Spring 4进行整合,这也是软件公司中非常常用的整合搭配。

在第12章中,读者将学习到最流行的Spring 4 MVC框架,体会使用此框架开发一个经典登录功能时使用的技术点,限制form提交方式,还要掌握分组分模块开发的技术,重定向/转发的使用,JSON+Ajax+Spring 4 MVC联合开发,上传/下载的实现,以及数据验证功能的使用。

第13章对Spring 4 MVC框架进行详细介绍,包括XML配置文件的处理,业务层Service的注入,ModelAndView对象的使用,以及HttpSession在Spring 4 MVC中的使用。

第14章讲解特别常用的Spring 4 MVC+MyBatis 3+Spring 4整合。本章以当前最具实战的组合框架来讲解整合的过程,而不囿于某一个框架本身,而且整合后的项目代码写法更加统一,便于维护与扩展。

首先需要声明,本书不是Java Web的入门教程,学习本书之前,读者先要对Java Web中的JSP、Servlet等Web技术有所了解,尽量能完整地使用JSP或Servlet开发一个小型项目,再阅读本书,那样会发现代码的分层更加明确,结构更加清楚。

软件开发实践才是硬道理,当读者认为阅读本书不成问题时,通常是因为读者的英语能力还不错,可以识别书中大多数英文的意思,但软件开发并不是完全考验英语能力,而是设计、排错,以及拥有更多的经验,所以请拿起手中的键盘,练习一下吧!

请尽量在自己的计算机中执行本书的所有代码,仅仅看书和自己动手存在天壤之别,动手运行代码和看书结合起来才能更深刻地理解框架的各项功能。

读者可从box.ptpress.com.cn/y/46361下载本书源代码。

由于Java EE内容涵盖面宽广,涉及的知识点非常多,并加之作者水平有限,错误之处在所难免,请广大读者在不足之处给予赐教和斧正。读者可以通过QQ(279377921)与我联系,期待与您进行交流。


本章讲解的是MyBatis 3框架,此框架的主要作用就是更加便携地操作数据库,比如可以将ResultSet对象返回的数据自动封装进Entity实体类或List中,可以把SQL语句配置到XML文件中,也就是将SQL语句与*.java文件进行分离,有利于代码的后期维护,也使代码的分层更加明确。另外由于MyBatis框架是使用SQL语句对数据库进行操作的,所以可以单独对SQL语句进行优化,以提高查询效率,这点与使用Hibernate框架相比有很大的优势,这也是为什么现阶段大部分的软件企业逐步用MyBatis替换掉Hibernate框架的主要原因。

MyBatis是一个操作数据库的框架,那什么是框架?框架就是软件功能的半成品。框架提供了一个软件项目中通用的功能,将大多数常见的功能进行封装,无需自己重复开发,框架增加了开发及运行效率。

需要说明的是,MyBatis并不是一个独立的技术,它内部操作数据库的原理还是使用JDBC,只是对JDBC进行了轻量级的封装,便于程序员更方便地设计代码去操作数据库。

在本章中,读者应该着重掌握如下内容:

为什么要使用MyBatis框架呢?这个答案真的有很多,作者认为其中最具有代表性的就是在使用传统的JDBC代码时,需要写上必要的DAO层代码,在DAO层的查询代码中将数据表中的数据封装到自定义的实体类中。这样的代码写法在软件开发的过程中非常不便,因为大部分的代码都是重复冗余的,几乎每个DAO类都在做同样的事情,但MyBatis解决了这类问题。使用MyBatis做查询时可以自动将数据表中的数据记录封装到实体类或Map中,再将它们放入List进行返回,这么常见而且有利于提高开发效率的功能MyBatis都可以自由方便地处理。从此观点来看,使用MyBatis框架去开发应用软件是非常方便快捷,MyBatis框架很有使用的必要性。

MyBatis是一个“持久化ORM框架”,持久化是指内存中的数据保存到硬盘上。ORM是对象关系映射(Object Relation Mapping),ORM的概念可以从2个方面来介绍:

也就是在使用MyBatis框架时,可以将Java类转化成数据表中的记录,或者将数据表中的记录转化成Java类,内部的技术原理其实就是JDBC+反射。这些功能都是由ORM框架来进行处理的,MyBatis可以实现这样的功能。MyBatis有不同的语言版本,比如.Net和Java都有对应的类库,它有大多数ORM框架都具有的功能,比如可以自定义SQL语句、调用存储过程、进行高级映射等。

下面来看一下ORM框架的技术原理,使用如下的代码进行实现。

创建类Userinfo.java代码如下:

package entity;

public class Userinfo {

    private long id;
    private String username;
    private String password;

    public Userinfo() {
    }

    public Userinfo(long id, String username, String password) {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
    }

    //get和set方法忽略
}

运行类代码如下:

package test;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import entity.Userinfo;

public class Test {

    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
        Userinfo userinfo = new Userinfo();
        userinfo.setId(100);
        userinfo.setUsername("中国");
        userinfo.setPassword("大中国");

        List valueList = new ArrayList();

        String sql = "insert into ";
        sql = sql + userinfo.getClass().getSimpleName().toLowerCase();

        String colSql = "";
        String valueSql = "";
        Field[] fieldArray = userinfo.getClass().getDeclaredFields();
        for (int i = 0; i < fieldArray.length; i++) {
            Field eachField = fieldArray[i];
            eachField.setAccessible(true);
            String fieldName = eachField.getName();
            colSql = colSql + "," + fieldName;
            valueSql = valueSql + ",?";
            Object value = fieldArray[i].get(userinfo);
            valueList.add(value);
        }
        colSql = colSql.substring(1);
        valueSql = valueSql.substring(1);
        colSql = "(" + colSql + ")";
        valueSql = "(" + valueSql + ")";
        sql = sql + colSql + " " + valueSql;
        System.out.println(sql);
        System.out.println();
        for (int i = 0; i < valueList.size(); i++) {
            Object value = valueList.get(i);
            System.out.println(value);

        }

    }

}

程序运行后在控制台打印信息如下:

insert into userinfo(id,username,password) (?,?,?)

100
中国
大中国

这就是将一个Userinfo.java对象转成添加到userinfo数据表的SQL语句,也是ORM框架的原理。

但更严格来讲,MyBatis是一种“半自动化”的ORM映射框架,它应该算作SQL映射框架(SQL mapper framework),官方也是这么介绍的,将MyBatis称为“半自动化的ORM框架”。这是因为MyBatis操作数据库时还是使用原始的SQL语句,这些SQL语句还是需要程序员自己来进行设计,这就是半自动化,只不过把重复的JDBC代码进行了封装与简化。在使用的方式上和全自动的ORM框架Hibernate有着非常大的区别,MyBatis是以SQL语句为映射基础,而Hibernate是彻底的基于实体类与表进行映射,基本是属于全自动化的ORM映射框架。但正是MyBatis属于半自动化的ORM映射框架这个特性,所以可以将SQL语句灵活多变的特性溶入到项目开发中。

另外,如果使用MyBatis框架,还可以省略大多数的JDBC代码,因为它把常用的JDBC操作都进行了封装,进而可以加快开发效率。MyBatis可以使用XML或Annotations注解的方式将数据表中的记录映射成1个Map或Java POJOs实体对象。但还是推荐使用XML的方式,该方式也是MyBatis官方推荐的。

由于MyBatis是第三方的框架,javaee.jar中并不包含它的API,所以得单独进行下载。

打开网页后看到的界面如图1-1所示。

图1-1 MyBatis官方网站

继续操作,单击Download Latest链接后打开的界面如图1-2所示。

图1-2 准备下载MyBatis框架

到这一步已经把MyBatis框架从官网下载到本地,然后就可以使用它的jar文件进行开发了。下载的zip压缩文件中包含开发时要用到的PDF文档、source源代码和jar文件等资源。

开门见山永远是快速学习一门技术最好的方式。

MyBatis框架的核心是SqlSessionFactory对象,从SqlSessionFactory类的名称来看,它是创建SqlSession对象的工厂。但SqlSessionFactory对象的创建来自于SqlSessionFactoryBuilder类,也就是使用SqlSessionFactoryBuilder类创建出SqlSessionFactory对象。

这3者之间的创建关系为:SqlSessionFactoryBuilder创建出SqlSessionFactory,SqlSessionFactory创建出SqlSession。

使用SqlSessionFactoryBuilder类创建SqlSessionFactory对象的方式可以来自于1个XML配置文件,还可以来自于1个实例化的Configuration对象,由于使用XML方式创建SqlSessionFactory对象在使用上比较广泛,所以在下面的小节就介绍这些内容。

创建名称为mybatis1的Web项目。

想要使用MyBatis实现CURD的操作,必须先要创建出SqlSessionFactory对象。

根据XML文件中的配置创建SqlSessionFactory对象的核心Test.java类的代码如下。

package test;

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

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Test {
    public static void main(String[] args) {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(inputStream);
            System.out.println(sqlSessionFactory);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上面代码的主要作用就是取得SqlSessionFactory工厂对象。其中mybatis-config.xml配置文件连接数据库的内容如下。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
</configuration>

配置文件mybatis-config.xml主要作用就是如何连接数据库,包含连接数据库所用到的username和password及url等参数,但并没有实质的属性值,而是使用${xxxx}做为替代。因为在获取SqlSessionFactory工厂对象时,不需要提供这些具体的参数值。

添加最新版的jar包的项目结构如图1-3所示。

图1-3 项目结构

来测试一下代码是否能正常创建出SqlSessionFactory类的实例,运行程序后并没有出现异常,输出信息如图1-4所示。

图1-4 输出SqlSessionFactory对象

DefaultSqlSessionFactory.java是SqlSessionFactory.java接口的实现类,具有实现关系,效果如图1-5所示。

图1-5 实现关系

到此,SqlSessionFactory对象来自于XML配置文件创建成功。

虽然现在正在学习新的知识,包括要熟悉新的类名,新的方法名称,新的包名等,但MyBatis在API的设计结构上是相当简洁的,大部分都是重载的方法。我们来看一看SqlSessionFactoryBuilder类的结构,效果如图1-6所示。

图1-6 SqlSessionFactoryBuilder类结构

SqlSessionFactory类的结构如图1-7所示。

图1-7 SqlSessionFactory类结构

从图1-6和图1-7可以看到,两者的类结构中基本全是重载的方法,主要就是通过build()方法取得SqlSessionFactory对象,使用openSession()方法取得SqlSession对象,对象SqlSession主要的作用是对数据库进行CURD操作。

使用MyBatis实现操作数据库要使用到SQL映射文件,但该文件的配置代码比较复杂,这种情况也存在于Hibernate框架中,所以为了加快开发效率,MyBatis官方提供1个Eclipse插件,名称为MyBatis Generator,该插件主要的功能就是生成SQL映射文件。此插件需要在Eclipse中在线安装,安装的过程请参考搜索引擎提供的资料,下面来看此插件的使用。

新建名为GeneratorOracle的Web项目,然后在Java项目的src结点下单击鼠标右键新建1个MyBatis的Generator配置文件,如图1-8所示。

图1-8 创建生成ORM的配置XML文件

单击“Next”按钮出现如图1-9所示界面。

在图1-9界面中不需要更改配置,保持默认设置即可,单击“Finish”按钮完成Generator配置文件的创建。

图1-9 将文件放入src下即可

对生成的generatorConfig.xml配置文件代码进行更改如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="context1">
        <jdbcConnection connectionURL="jdbc:oracle:thin:@localhost:1521:orcl"
            driverClass="oracle.jdbc.OracleDriver" password="123" userId="y2" />
        <javaModelGenerator targetPackage="sqlmapping"
            targetProject="GeneratorOracle" />
        <sqlMapGenerator targetPackage="sqlmapping"
            targetProject="GeneratorOracle" />
        <javaClientGenerator targetPackage="sqlmapping"
            targetProject="GeneratorOracle" type="XMLMAPPER" />
        <table schema="y2" tableName="userinfo">
        </table>
    </context>
</generatorConfiguration>

配置文件generatorConfig.xml是MyBatis Generator插件中必备的文件,通过此文件可以将数据表的结构逆向出对应的Java类以及存储SQL语句的SQL映射文件,然后用MyBatis的API就可以对这些Java类进行操作,从而演变成对数据表的增删改查操作。

数据表userinfo的表结构如图1-10所示。

图1-10 userinfo数据表结构

配置文件generatorConfig.xml准备就绪后单击图1-11中的菜单。

图1-11 根据XML配置文件生成ORM映射文件

这里却出现了异常情况,效果如图1-12所示。

图1-12 找不到JDBC驱动

下一步就要在Run As中添加JDBC驱动,单击如图1-13所示的菜单。

图1-13 在Run As中配置JDBC驱动

弹出配置界面如图1-14所示,按(1)、(2)、(3)、(4)的步骤将ojdbc6.jar添加到classpath中。

图1-14 添加驱动jar包

驱动jar包添加成功后单击右下角的Run按钮开始逆向,逆向后的项目结构如图1-15所示。

图1-15 成功生成ORM映射文件

文件UserinfoMapper.java中出现了异常,原因是并没有添加MyBatis的jar包,添加成功后Java文件不再出现红叉,如图1-16所示。

图1-16 MyEclipse中Web项目结构

至此MyBatis操作数据库的基础文件已经准备完毕。

下面开始在Oracle数据库的userinfo数据表中添加1条记录。

在src中创建mybatis-config.xml文件,代码如下。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

注意:在mybatis-config.xml配置文件中添加如下配置。

    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>

文件UserinfoMapper.xml中存储着SQL语句,所以在配置文件中需要关联。

创建Test.java运行类代码如下。

package test;

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

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 sqlmapping.Userinfo;

public class Test {

    public static void main(String[] args) {
        try {

            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("usernameValue");
            userinfo.setPassword("passwordValue");
            userinfo.setAge(100L);

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            sqlSession.insert("insert", userinfo);
            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

方法main()中的代码是一个经典的insert数据表的功能,从代码中可以看到MyBatis用最精简的API就可以完全地控制数据表中的记录,可见MyBatis不管是在学习还是开发等方面成本都是比较低的。

程序运行后在控制台打印出的异常信息如下。

Caused by: java.sql.SQLException: Error setting driver on UnpooledDataSource. Cause: java.lang.ClassNotFoundException: Cannot find class: oracle.jdbc.OracleDriver。

异常信息提示该项目中并没有添加JDBC的驱动jar文件,添加ojdbc6.jar并将其添加进classpath中再运行Test.java时依然出现了异常提示,如图1-17所示。

图1-17 不能对id主键列赋null空值

Oracle数据库的主键并不是自增的,在insert语句中需要结合序列来实现添加记录的功能,逆向生成错误的insert语句如图1-18所示。

图1-18 SQL语句中没有使用序列

这说明sqlmapping包中的文件都是错误的,所以需要连同sqlmapping包与下面的所有文件一同进行删除,因为要重新进行逆向了。

在逆向之前需要更改generatorConfig.xml配置文件中的配置,添加<generatedKey>标签后的配置代码如下。

        <table schema="y2" tableName="userinfo">
            <generatedKey column="id" sqlStatement="select idauto.nextval from dual"
                identity="false" />
        </table>

标签<generatedKey>的主要作用就是在生成insert的SQL语句时使用名称为idauto序列的nextval值来做为主键id值,identity="false"属性说明主键并不是自增的,而是由序列生成的。

如果想同时逆向多个表,则在generatorConfig.xml文件中写入多个<table>标签即可,示例代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="context1">
        <jdbcConnection connectionURL="jdbc:oracle:thin:@localhost:1521:orcl"
            driverClass="oracle.jdbc.OracleDriver" password="123" userId="y2" />
        <javaModelGenerator targetPackage="mapping"
            targetProject="GeneratorOracle" />
        <sqlMapGenerator targetPackage="mapping"
            targetProject="GeneratorOracle" />
        <javaClientGenerator targetPackage="mapping"
            targetProject="GeneratorOracle" type="XMLMAPPER" />
        <table schema="y2" tableName="userinfo">
            <generatedKey column="id" sqlStatement="select idauto.nextval from dual"
                identity="false" />
        </table>
        <table schema="y2" tableName="A">
            <generatedKey column="id" sqlStatement="select idauto.nextval from dual"
                identity="false" />
        </table>
        <table schema="y2" tableName="B">
            <generatedKey column="id" sqlStatement="select idauto.nextval from dual"
                identity="false" />
        </table>
    </context>
</generatorConfiguration>

配置文件generatorConfig.xml准备结束后重新进行逆向操作,在sqlmapping包中生成最新版正确的.java和.xml文件,最新版的insert语句如图1-19所示。

图1-19 新版的insert语句

再次运行Test.java类,成功在数据表中添加了1条记录,如图1-20所示。

图1-20 成功添加1条记录

MySQL数据库中的userinfo数据表结构如图1-21所示。

图1-21 数据表userinfo结构

创建名称为GeneratorMySQL的项目,更改配置文件generatorConfig.xml的代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="context1">
        <jdbcConnection connectionURL="jdbc:mysql://localhost:3306/y2"
            driverClass="com.mysql.jdbc.Driver" password="123" userId="root" />
        <javaModelGenerator targetPackage="sqlmapping"
            targetProject="GeneratorMySQL" />
        <sqlMapGenerator targetPackage="sqlmapping"
            targetProject="GeneratorMySQL" />
        <javaClientGenerator targetPackage="sqlmapping"
            targetProject="GeneratorMySQL" type="XMLMAPPER" />
        <table schema="y2" tableName="userinfo">
            <generatedKey column="id" sqlStatement="mysql" identity="true" />
        </table>
    </context>
</generatorConfiguration>

对generatorConfig.xml文件进行逆向。

在src中创建mybatis-config.xml文件,代码如下。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/y2" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

运行Test.java类代码如下。

package test;

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

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 sqlmapping.Userinfo;

public class Test {

    public static void main(String[] args) {
        try {

            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("usernameValue");
            userinfo.setPassword("passwordValue");
            userinfo.setAge(888);

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            sqlSession.insert("insert", userinfo);
            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

运行Test.java类后在userinfo数据表添加了新的记录,效果如图1-22所示。

图1-22 数据表userinfo中的新记录

前面都是使用MyBatis Generator工具生成实体和SQL映射文件,并不能从基础上掌握MyBatis框架的使用,本小节将从0起步开始研究如何使用MyBatis框架实现经典功能CURD,主要针对2种主流数据库。

MyBatis框架针对每一种数据库的操作都大同小异,本节将用示例的方式演示Oracle数据库的CURD操作。

1.准备开发环境

(1)创建数据表。

创建userinfo数据表,表结构如图1-23所示。

图1-23 userinfo数据表结构

(2)准备generatorConfig.xml逆向配置文件。

(3)创建名称为mybatis_curd_oracle的Web项目。

(4)创建Userinfo.java实体类,实体类Userinfo.java类结构如图1-24所示。

图1-24 Userinfo.java类结构

(5)项目结构如图1-25所示。

图1-25 Eclipse中的orm包中有实体

(6)在Web项目mybatis_curd_oracle中的src路径下创建连接数据库的配置文件mybatis-config.xml,代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1522:accp11g" />
                <property name="username" value="ghy" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="userinfoMapping.xml" />
    </mappers>
</configuration>

其中userinfoMapping.xml映射文件内容如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">
    <insert id="insertUserinfo" parameterType="orm.Userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
         </selectKey>
        insert into
        userinfo(id,username,password,age,insertDate)
        values(#{id},#{username},#{password},#{age},#{insertdate})
    </insert>
    <select id="getUserinfoById" parameterType="int" resultType="orm.Userinfo">
        select * from
        userinfo where id=#{id}
    </select>
    <delete id="deleteUserinfoById" parameterType="int">
        delete from
        userinfo where id=#{id}
    </delete>
    <select id="getAllUserinfo" resultType="orm.Userinfo">
        select * from userinfo
    </select>
    <update id="updateUserinfoById" parameterType="orm.Userinfo">
        update userinfo
        set
        username=#{username},password=#{password},age=#{age},insertDate=#{insertdate}
        where id=#{id}
    </update>
</mapper>

需要特别说明的是,如果SQL语句有一些特殊符号,则必须使用如下的格式进行设计SQL语句。

<![CDATA[ sql语句 ]]>

其中配置代码如下所示。

        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
         </selectKey>

标记<selectKey>的order="BEFORE"属性含义是select语句比insert语句先执行,resultType属性值为"java.lang.Long"是将序列返回的数字转成Long类型,keyProperty="id"的作用是将这个Long值放入parameterType的Userinfo的id属性里面。此段配置的主要功能是根据序列对象生成一个主键id值,并且此值还可以在代码中获取,也就是插入一条记录后使用程序代码就可以获取刚才插入记录的id值。

属性parameterType定义参数类型,属性resultType定义返回值的类型。

继续创建测试用的Servlet对象,完整的项目结构如图1-26所示。

图1-26 完整项目结构

在图中可以发现src路径下有两个dtd文件,这是为了在开发XML配置或XML映射文件时实现代码自动提示功能。

2.创建获取SqlSession对象工具类

核心代码如下。

package dbtools;

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

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public abstract class GetSqlSession {

    public static SqlSession getSqlSession() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        SqlSession sqlSsession = sqlSessionFactory.openSession();
        return sqlSsession;
    }

}

3.插入多条记录

创建Servlet,核心代码如下。

public class insertUserinfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("高洪岩");
            userinfo.setPassword("岩洪高");
            userinfo.setAge(100L);
            userinfo.setInsertdate(new Date());

            SqlSession sqlSession = GetSqlSession.getSqlSession();
            sqlSession.insert("mybatis.testcurd.insertUserinfo", userinfo);
            System.out.println(userinfo.getId());
            sqlSession.commit();
            sqlSession.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

变量SqlSession的insert方法的第1个参数是userinfoMapping.xml映射文件<insert>标签的id值,还要加上namespace命名空间的前缀,映射文件部分代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">
    <insert id="insertUserinfo" ……>

在代码中可以看到获取已经插入数据表中记录的主键值。

执行Servlet后在控制台输出如图1-27所示。

图1-27 插入多条记录

Oracle数据库中的userinfo数据表内容如图1-28所示。

图1-28 出现3条数据记录

4.根据id值查询记录

创建Servlet,核心代码如下。

public class getUserinfoById extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            SqlSession sqlSession = GetSqlSession.getSqlSession();
            Userinfo userinfo = sqlSession.selectOne(
                    "mybatis.testcurd.getUserinfoById", 7);
            System.out.println(userinfo.getId());
            System.out.println(userinfo.getUsername());
            System.out.println(userinfo.getPassword());
            System.out.println(userinfo.getAge());
            System.out.println(userinfo.getInsertdate().toLocaleString());
            sqlSession.commit();
            sqlSession.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

程序运行后的效果如图1-29所示。

图1-29 打印id是7的信息

5.查询所有记录

创建Servlet,核心代码如下。

public class getAllUserinfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {

            SqlSession sqlSession = GetSqlSession.getSqlSession();
            List<Userinfo> listUserinfo = sqlSession
                    .selectList("mybatis.testcurd.getAllUserinfo");
            for (int i = 0; i < listUserinfo.size(); i++) {
                Userinfo userinfo = listUserinfo.get(i);
                System.out.println(userinfo.getId() + " "
                        + userinfo.getUsername() + " " + userinfo.getPassword()
                        + " " + userinfo.getAge() + " "
                        + userinfo.getInsertdate().toLocaleString());

            }
            sqlSession.commit();
            sqlSession.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

程序运行后打印出3条记录,如图1-30所示。

图1-30 打印3条记录信息

6.更新记录

创建Servlet,核心代码如下。

public class updateUserinfoById extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            SqlSession sqlSession = GetSqlSession.getSqlSession();
            Userinfo userinfo = sqlSession.selectOne(
                    "mybatis.testcurd.getUserinfoById", 7);
            userinfo.setUsername("最新版高洪岩");
            sqlSession.update("mybatis.testcurd.updateUserinfoById", userinfo);

            sqlSession.commit();
            sqlSession.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

程序运行后数据表userinfo中的记录被更新,如图1-31所示。

图1-31 userinfo数据表内容被更新

7.删除记录

创建Servlet,核心代码如下。

public class deleteUserinfoById extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            SqlSession sqlSession = GetSqlSession.getSqlSession();
            sqlSession.delete("mybatis.testcurd.deleteUserinfoById", 6);
            sqlSession.commit();
            sqlSession.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

程序运行后,将id为6的记录删除了,如图1-32所示。

图1-32 不存在id为6的记录

至此,针对Oracle数据库的CURD操作到此结束。

本章节也将从零开始,使用MyBatis操作MySQL数据库。

1.准备开发环境

(1)在Eclipse中创建名称为mybatis_curd_mysql的项目。

(2)创建Userinfo.java实体类。

(3)创建mybatis-config.xml配置文件,代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3307/ghydb" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="userinfoMapping.xml" />
    </mappers>
</configuration>

(4)创建SQL映射文件userinfoMapping.xml,代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">
    <insert id="insertUserinfo" parameterType="orm.Userinfo"
        useGeneratedKeys="true" keyProperty="id">
        insert into
        userinfo(username,password,age,insertDate)
        values(#{username},#{password},#{age},#{insertdate})
    </insert>
    <select id="getUserinfoById" parameterType="int" resultType="orm.Userinfo">
        select * from
        userinfo where id=#{id}
    </select>
    <delete id="deleteUserinfoById" parameterType="int">
        delete from
        userinfo where id=#{id}
    </delete>
    <select id="getAllUserinfo" resultType="orm.Userinfo">
        select * from userinfo
    </select>
    <update id="updateUserinfoById" parameterType="orm.Userinfo">
        update userinfo
        set
        username=#{username},password=#{password},age=#{age},insertDate=#{insertdate}
        where id=#{id}
    </update>
</mapper>

MySQL的主键是自增的,所以不需要序列这种机制。

2.增加一条记录并且返回主键值

创建Servlet对象,插入记录的代码和Oracle数据库对应的Servlet代码一致,运行后在控制台打印4条记录的主键ID值,效果如图1-33所示。

图1-33 向MySQL数据库中插入4条记录

数据表userinfo中的内容如图1-34所示。

图1-34 数据表userinfo中的4条记录

3.其他业务方法的测试

其他的业务方法代码和操作Oracle数据库的大体一致,并且已经成功运行,详细代码请参看随书下载的源代码。

当多个SQL映射文件中的id值一样时,在使用SqlSession操作数据库时会出现异常。

创建测试用的namespaceError项目,创建userinfoMapping.xml映射文件代码如下。

<mapper namespace="AAAAA">
    <insert id="insertUserinfo" parameterType="orm.Userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})

    </insert>
</mapper>

再创建名称为userinfoMappingZZZZZZZZ.xml映射文件,代码如下。

<mapper namespace="BBBBB">
    <insert id="insertUserinfo" parameterType="orm.Userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})
    </insert>
</mapper>

将这2个SQL映射文件使用<mapper>标签注册到mybatis-config.xml配置文件中,代码如下。

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="userinfoMapping.xml" />
        <mapper resource="userinfoMappingZZZZZZZZ.xml" />
    </mappers>
</configuration>

在2个SQL映射文件中都有insert的id属性值为insertUserinfo的配置代码,执行如下Java代码。

public class insertUserinfo {

    public static void main(String[] args) {
        try {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("高洪岩");
            userinfo.setPassword("岩洪高");
            userinfo.setAge(100L);
            userinfo.setInsertdate(new Date());

            SqlSession sqlSession = GetSqlSession.getSqlSession();
            sqlSession.insert("insertUserinfo", userinfo);
            System.out.println(userinfo.getId());
            sqlSession.commit();
            sqlSession.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

执行后出现了异常,异常信息如下。

java.lang.IllegalArgumentException: insertUserinfo is ambiguous in Mapped Statements collection (try using the full name including the namespace, or rename one of the entries)

异常信息提示insertUserinfo并不是确定的名字,是模糊的,可以尝试使用全名称的方式来调用此SQL映射,所谓的“全名称”方式就是指sqlId之前写上namspace命名空间,对namespace命名空间的命名可以写上表的名称,或者是业务的名称,这样有助于区分重复的sqlId。

更改后的Java代码如下。

public class insertUserinfoOK {

    public static void main(String[] args) {
        try {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("高洪岩");
            userinfo.setPassword("岩洪高");
            userinfo.setAge(100L);

            SqlSession sqlSession = GetSqlSession.getSqlSession();
            sqlSession.insert("BBBBB.insertUserinfo", userinfo);
            System.out.println(userinfo.getId());
            sqlSession.commit();
            sqlSession.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

执行insert()方法时在第1个参数加上了命名空间BBBBB,使用小数点(.)做为间隔,再执行程序就不会出现异常了。

在前面对2种主流数据库实现基本的CURD后,对MyBatis核心对象在使用上应该不再陌生,在本中将会继续介绍一下这些核心对象的生命周期。

对象的生命周期也就是对象从创建到销毁的过程,但在此过程中,如果实现的代码质量不太优质,那么很容易造成程序上的错误或效率的降低。

public class insertUserinfo extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        SqlSession sqlSession = GetSqlSession.getSqlSession();
        try {
            // sqlSession curd code
            sqlSession.commit();
        } catch (Exception e) {
            sqlSession.rollback();
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }

    }
}

根据前面学习过的生命周期的知识,在后面的章节将对MyBatis核心代码进行封装,这样更有助于对数据CURD的方便性,创建Web项目,名称为mybatis_threadlocal。

创建GetSqlSessionFactory.java类,完整代码如下。

package dbtools;

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

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class GetSqlSessionFactory {

    private static SqlSessionFactory sqlSessionFactory;

    private GetSqlSessionFactory() {
    }

    synchronized public static SqlSessionFactory getSqlSessionFactory() {

        try {
            if (sqlSessionFactory == null) {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources
                        .getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder()
                        .build(inputStream);
            } else {
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return sqlSessionFactory;
    }

}

在GetSqlSessionFactory.java类中使用单例设计模式来取得SqlSessionFactory对象。

创建GetSqlSession.java类的核心代码如下。

package dbtools;

import org.apache.ibatis.session.SqlSession;

public class GetSqlSession {

    private static ThreadLocal<SqlSession> tl = new ThreadLocal<SqlSession>();

    public static SqlSession getSqlSession() {
        SqlSession sqlSession = tl.get();
        if (sqlSession == null) {
            sqlSession = GetSqlSessionFactory.getSqlSessionFactory()
                    .openSession();
            tl.set(sqlSession);
        } else {

        }
        System.out.println("获得的sqlSession对象的hashCode:" + sqlSession.hashCode());
        return sqlSession;
    }

    public static void commit() {
        if (tl.get() != null) {
            tl.get().commit();
            tl.get().close();
            tl.set(null);
            System.out.println("提交了");
        }
    }

    public static void rollback() {
        if (tl.get() != null) {
            tl.get().rollback();
            tl.get().close();
            tl.set(null);
            System.out.println("回滚了");
        }
    }

}

创建DBOperate.java类核心代码如下。

package dbtools;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;

public class DBOperate {

    public int insert(String sqlId, Map valueMap) {
        SqlSession sqlSession = GetSqlSession.getSqlSession();
        return sqlSession.insert(sqlId, valueMap);
    }

    public int delete(String sqlId, Map valueMap) {
        SqlSession sqlSession = GetSqlSession.getSqlSession();
        return sqlSession.delete(sqlId, valueMap);
    }

    public int update(String sqlId, Map valueMap) {
        SqlSession sqlSession = GetSqlSession.getSqlSession();
        return sqlSession.update(sqlId, valueMap);
    }

    public List<Map> select(String sqlId, Map valueMap) {
        SqlSession sqlSession = GetSqlSession.getSqlSession();
        return sqlSession.selectList(sqlId, valueMap);
    }

}

所有CURD的参数值都用Map对象进行封装,所以看一下SQL映射文件中的代码吧。

创建userinfoMapping.xml映射文件的代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">
    <insert id="insertUserinfo" parameterType="map"
        useGeneratedKeys="true" keyProperty="id">
        insert into
        userinfo(username,password,age,insertDate)
        values(#{username},#{password},#{age},#{insertdate})
    </insert>
    <select id="getUserinfoById" parameterType="map" resultType="map">
        select * from
        userinfo where id=#{id}
    </select>
    <delete id="deleteUserinfoById" parameterType="map">
        delete from
        userinfo where id=#{id}
    </delete>
    <select id="getAllUserinfo" resultType="map">
        select * from userinfo
    </select>
    <update id="updateUserinfoById" parameterType="map">
        update userinfo
        set
        username=#{username},password=#{password},age=#{age},insertDate=#{insertdate}
        where id=#{id}
    </update>
</mapper>

创建连接数据库的mybatis-config.xml配置文件,代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver"
                    value="自定义的值" />
                <property name="url"
                    value="自定义的值" />
                <property name="username" value="自定义的值" />
                <property name="password" value="自定义的值" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="userinfoMapping.xml" />
    </mappers>
</configuration>

该对象的主要作用就是测试在1个请求中多次获取SqlSession对象是不是1个,核心代码如下。

public class test extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        GetSqlSession.getSqlSession();
        GetSqlSession.getSqlSession();
        GetSqlSession.getSqlSession();
        GetSqlSession.getSqlSession();
        GetSqlSession.getSqlSession();
    }
}

程序运行后,在控制台输出信息如图1-35所示。

图1-35 获得的SqlSession对象是1个

添加记录及异常回滚的测试,核心代码如下。

public class insert extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            HashMap valueMap1 = new HashMap();
            valueMap1.put("username", "高洪岩今天1");
            valueMap1.put("password", "高洪岩明天1");
            valueMap1.put("age", 100);
            valueMap1.put("insertdate", new Date());

            HashMap valueMap2 = new HashMap();
            valueMap2.put("username", "高洪岩今天2");
            valueMap2.put("password", "高洪岩明天2");
            valueMap2.put("age", 100);
            valueMap2.put("insertdate", new Date());

            DBOperate dbo = new DBOperate();
            dbo.insert("insertUserinfo", valueMap1);
            dbo.insert("insertUserinfo", valueMap2);
        } catch (Exception e) {
            e.printStackTrace();
            GetSqlSession.rollback();
        } finally {
            GetSqlSession.commit();
        }

    }
}

程序运行后,在控制台输出信息如图1-36所示。

图1-36 控制台输出信息

数据表中的数据如图1-37所示。

图1-37 成功添加2条记录

上面的步骤证明添加多条记录成功,userinfo数据表中有2条记录。

再来测试异常回滚的情况,更改部分代码如下。

            HashMap valueMap2 = new HashMap();
            valueMap2.put("username",
                    "高洪岩今天2_123456789_123456789_123456789_123456789_123456789");
            valueMap2.put("password", "高洪岩明天2");
            valueMap2.put("age", 100);
            valueMap2.put("insertdate", new Date());

程序运行后在控制台输出异常信息如下。

获得的sqlSession对象的hashCode:24442607
获得的sqlSession对象的hashCode:24442607
org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 将截断字符串或二进制数据。
### The error may involve mybatis.testcurd.insertUserinfo-Inline
### The error occurred while setting parameters
### SQL: insert into   userinfo(username,password,age,insertDate)   values(?,?,?,?)
### Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 将截断字符串或二进制数据。
    at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:147)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:134)
    at dbtools.DBOperate.insert(DBOperate.java:12)
    at controller.insert.doGet(insert.java:36)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:690)
rotocol.java:624)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:445)
    at java.lang.Thread.run(Thread.java:619)
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 将截断字符串或二进制数据。
    at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:145)
    ... 17 more
回滚了

通过上面的信息可以得知,程序出现异常,并且已经回滚,那userinfo数据表中是否还是2条记录呢?查看一下,其内容如图1-38所示。

图1-38 成功回滚后还是2条记录

在userinfo数据表中多增加几条记录,便于后面的测试,新增的记录如图1-39所示。

图1-39 userinfo表中的多条记录

删除记录的核心代码如下。

public class delete extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            HashMap valueMap1 = new HashMap();
            valueMap1.put("id", 44);

            DBOperate dbo = new DBOperate();
            dbo.delete("deleteUserinfoById", valueMap1);
        } catch (Exception e) {
            e.printStackTrace();
            GetSqlSession.rollback();
        } finally {
            GetSqlSession.commit();
        }

    }

}

程序运行后,userinfo数据表中的记录如图1-40所示。

图1-40 成功删除id为44的记录

更改记录的核心代码如下。

public class update extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            HashMap valueMap1 = new HashMap();
            valueMap1.put("id", 45);
            valueMap1.put("username", "高洪岩今天3new");
            valueMap1.put("password", "高洪岩明天3new");
            valueMap1.put("age", 100);
            valueMap1.put("insertdate", new Date());

            DBOperate dbo = new DBOperate();
            dbo.update("updateUserinfoById", valueMap1);
        } catch (Exception e) {
            e.printStackTrace();
            GetSqlSession.rollback();
        } finally {
            GetSqlSession.commit();
        }

    }

}

程序运行后,userinfo表中的数据如图1-41所示。

图1-41 userinfo表中的记录

查询单条记录的核心代码如下。

public class getUserinfoById extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            HashMap valueMap1 = new HashMap();
            valueMap1.put("id", 39);

            DBOperate dbo = new DBOperate();
            List<Map> list = dbo.select("getUserinfoById", valueMap1);
            for (int i = 0; i < list.size(); i++) {
                Map rowMap = list.get(i);
                System.out.println(rowMap.get("id") + "_"
                        + rowMap.get("username") + "_" + rowMap.get("password")
                        + "_" + rowMap.get("age") + "_"
                        + rowMap.get("insertDate"));
            }
        } catch (Exception e) {
            e.printStackTrace();
            GetSqlSession.rollback();
        } finally {
            GetSqlSession.commit();
        }

    }

}

程序运行后,在控制台输出如图1-42所示的结果。

图1-42 控制台输出信息

查询多条记录的核心代码如下。

public class getAllUserinfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            DBOperate dbo = new DBOperate();
            List<Map> list = dbo.select("getAllUserinfo", null);
            for (int i = 0; i < list.size(); i++) {
                Map rowMap = list.get(i);
                System.out.println(rowMap.get("id") + "_"
                        + rowMap.get("username") + "_" + rowMap.get("password")
                        + "_" + rowMap.get("age") + "_"
                        + rowMap.get("insertDate"));
            }
        } catch (Exception e) {
            e.printStackTrace();
            GetSqlSession.rollback();
        } finally {
            GetSqlSession.commit();
        }

    }

}

程序运行后,控制台的输出如图1-43所示。

图1-43 控制台输出多条记录信息

ORM框架MyBatis介绍到这里,读者应该能熟练地使用它进行数据库的CURD操作,并且对核心API在使用上有一个了解。


本章内容是学习MyBatis框架必须要掌握的技能,这些技能在使用MyBatis框架时使用率是非常得多。可以这样说,MyBatis大部分核心功能都在本章中进行介绍。

前面知识点中的代码都是使用如下程序对数据库进行操作。

    sqlSession.insert("sqlId");
    sqlSession.delete("sqlId");
    sqlSession.update("sqlId");
    sqlSession.selectList("sqlId");

对方法insert、delete、update和selectList传入sqlId来达到调用SQL语句而对数据库的操作目的,完完全全是在面向String字符串类型的sqlId编程,虽然能达到实现操作数据库的目的,但这种代码写法却是不规范的。理想中规范的写法不是面向sqlId编程,而是面向接口编程,新版的MyBatis提供了“接口-SQL映射”的功能,使程序员完全面向接口进行编程,相比sqlId的方式,使用“接口-SQL映射”在代码规范上更上一个台阶。

“接口-SQL映射”的对应关系如图2-1所示。

“接口-SQL映射”的原理如下:

SQL映射文件UserinfoMapper.xml中的namespace属性值sqlmapping.UserinfoMapper代表该映射对应的就是sqlmapping包中的UserinfoMapper.java接口,而<insert>标签的id属性值insertUserinfo就是UserinfoMapper.java接口中的public void insertUserinfo(Userinfo userinfo)方法,<insert>标签的parameterType属性值sqlmapping.Userinfo就是public void insertUserinfo(Userinfo userinfo)方法参数类型,只要它们一一对应,就能实现“接口-SQL映射”,程序员完全以面向接口的方式设计软件。

下面来看一下详细代码吧!

图2-1 接口-SQL映射原理

创建名称为getMapperTest的项目。

创建实体类Userinfo.java,代码如下。

package sqlmapping;

public class Userinfo {

    private Long id;
    private String username;
    private String password;
    private Long age;

   //省略set和get方法

}

程序员需要使用该接口进行操作数据库,示例代码如下。

package sqlmapping;

import java.util.List;

public interface UserinfoMapper {

    public void insertUserinfo(Userinfo userinfo);

    public List<Userinfo> getAllUserinfo();

    public Userinfo getUserinfoById(long userId);

    public void deleteUserinfoById(long userId);

    public void updateUserinfoById(Userinfo userinfo);

}

最为关键的SQL映射文件UserinfoMapper.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="sqlmapping.UserinfoMapper">
    <insert id="insertUserinfo" parameterType="sqlmapping.Userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})
    </insert>
    <select id="getUserinfoById" parameterType="long" resultType="sqlmapping.Userinfo">
        select * from
        userinfo where id=#{id}
    </select>
    <delete id="deleteUserinfoById" parameterType="long">
        delete from
        userinfo where id=#{id}
    </delete>
    <select id="getAllUserinfo" resultType="sqlmapping.Userinfo">
        select * from userinfo
    </select>
    <update id="updateUserinfoById" parameterType="sqlmapping.Userinfo">
        update userinfo
        set
        username=#{username},password=#{password},age=#{age}
        where id=#{id}
    </update>
</mapper>

SQL与接口的映射对应关系一定要配置成功,不然程序就会出现异常。

增加insert的操作代码如下。

package test;

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

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 sqlmapping.Userinfo;
import sqlmapping.UserinfoMapper;

public class InsertUserinfo {

    public static void main(String[] args) {
        try {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("高洪岩");
            userinfo.setPassword("大中国");
            userinfo.setAge(100L);

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();

            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            userinfoMapper.insertUserinfo(userinfo);

            sqlSession.commit();
            sqlSession.close();

            System.out.println("createId=" + userinfo.getId());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

接口UserinfoMapper.java并不能直接使用,必须得有接口UserinfoMapper.java的实现类才可以执行任务。MyBatis动态地创建出了接口UserinfoMapper.java的实现类,示例代码如下。

    UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);
    System.out.println(userinfoMapper);

程序运行结果如图2-2所示。

图2-2 对象userinfoMapper是代理类

控制台输出来的“org.apache.ibatis.binding.MapperProxy”类就是被MyBatis动态创建出来的,而且实现了接口UserinfoMapper.java,所以才可以对UserinfoMapper对象进行赋值,属于多态关系。

查询全部selectAll的操作代码如下。

public class GetAllUserinfo {

    public static void main(String[] args) {
        try {

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            List<Userinfo> listUserinfo = userinfoMapper.getAllUserinfo();
            for (int i = 0; i < listUserinfo.size(); i++) {
                Userinfo userinfo = listUserinfo.get(i);
                System.out.println(userinfo.getId() + " " + userinfo.getUsername() + " " + userinfo.getPassword() + " "
                        + userinfo.getAge());

            }

            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

查询单条记录selectById的操作代码如下。

public class GetUserinfoById {

    public static void main(String[] args) {
        try {

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            Userinfo userinfo = userinfoMapper.getUserinfoById(1445436L);

            System.out.println(userinfo.getId() + " " + userinfo.getUsername() + " " + userinfo.getPassword() + " "
                    + userinfo.getAge());

            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

修改记录updateById的操作代码如下。

public class UpdateUserinfoById {

    public static void main(String[] args) {
        try {

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            Userinfo userinfo = userinfoMapper.getUserinfoById(1445436L);

            userinfo.setUsername("新的账号");
            userinfo.setPassword("新的密码");
            userinfo.setAge(888L);

            userinfoMapper.updateUserinfoById(userinfo);

            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

删除记录deleteById的操作代码如下。

public class DeleteUserinfoById {

    public static void main(String[] args) {
        try {

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            userinfoMapper.deleteUserinfoById(1445434L);

            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

程序员一直在使用接口进行编程,不再使用sqlId进行软件设计。

在执行select查询或insert添加的SQL语句时,都要在parameterType或resultType属性中写上完整的实体类路径,路径中需要包含完整的包名,示例代码如下。

    <insert id="insertUserinfo" parameterType="sqlmapping.Userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})
    </insert>
    <select id="getUserinfoById" parameterType="long" resultType="sqlmapping.Userinfo">
        select * from
        userinfo where id=#{id}
    </select>

如果包名嵌套层级较多,则会出现大量冗余的配置代码,这时可以在mybatis-config.xml配置文件中使用<typeAliases>标签来简化。

本节的示例代码在名称为typeAliasTest的项目中,在mybatis-config.xml配置文件中添加<typeAlias>标签,代码如下。

<configuration>
    <typeAliases>
        <typeAlias alias="userinfo" type="sqlmapping.Userinfo" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

其中配置代码<typeAlias alias="userinfo" type="sqlmapping.Userinfo" />中的type属性值就是完整的实体类包路径,而属性alias就是实体类的别名,这个别名可以在SQL映射文件中进行使用,从而简化了配置代码,示例代码如下。

<mapper namespace="sqlmapping.UserinfoMapper">
    <insert id="insertUserinfo" parameterType="userinfo">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})
    </insert>
    <select id="getAllUserinfo" resultType="userinfo">
        select * from
        userinfo
    </select>
</mapper>

使用类型别名typeAlias后,即可以正常地操作数据库,配置代码也不再出现冗余了。

在引用别名时是不区分大小写的,比如如下代码也能正确得到运行。

parameterType="USERinfo"
resultType="USERINFo"

使用<typeAlias>虽然可以实现配置别名,但如果实体类的数量较多,则极易出现<typeAlias>配置爆炸,这种情况可以通过使用<package>标签来解决,它的原理就是扫描指定包下的类,这些类都被自动赋于了与类同名的别名,不区分大小写,别名中不包含包名。

创建名称为typeAliasPackageTest的项目,更改mybatis-config.xml配置文件,代码如下。

<configuration>
    <typeAliases>
        <package name="sqlmapping" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

在<typeAliases>标签中使用<package>子标签来扫描包中的类而自动创建出类的别名。

SQL映射文件也需要进行更改,代码如下。

<mapper namespace="sqlmapping.UserinfoMapper">
    <insert id="insertUserinfo" parameterType="usERInFO">
        <selectKey resultType="java.lang.Long" keyProperty="id"
            order="BEFORE">
            select idauto.nextval from dual
        </selectKey>
        insert into
        userinfo(id,username,password,age)
        values(#{id},#{username},#{password},#{age})
    </insert>
    <select id="getAllUserinfo" resultType="Userinfo">
        select * from
        userinfo
    </select>
</mapper>

在SQL映射文件中使用配置parameterType="usERInFO"也能正确地操作数据库,说明使用<package>方式定义的别名是不区分大写的。

另外在使用<package>包扫描时,代码如下。

    <typeAliases>
        <package name="entity1" />
        <package name="entity2" />
</typeAliases>

如果在不同的包中出现相同实体类名的情况下,在MyBatis解析XML配置文件时就会出现异常信息。

Caused by: org.apache.ibatis.type.TypeException: The alias 'Userinfo' is already mapped to the value 'entity1.Userinfo'.

此结论在项目typeAliasPackageTest2中得到验证。

配置文件mybatis-config.xml承载的配置信息过多,所以可以将连接数据库的信息转移到properties属性文件中,这样也便于代码的后期维护与管理。

在前面章节示例中,连接数据库时的具体参数是直接在mybatis-config.xml文件中进行定义的,比如url.username和password这些信息,MyBatis还支持将这些参数值写入*.properties属性文件中,然后使用Java代码对这些参数值进行传递。

更改mybatis-config.xml配置文件的部分代码如下。

    <dataSource type="POOLED">
        <property name="driver" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
    </dataSource>

在src中创建db.properties属性文件,内容如下。

url=jdbc:sqlserver://localhost:1079;databaseName=ghydb
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
username=sa
password=

使用如下Java代码即可对类似${driver}的表达式传值了。

    InputStream isRef = GetSqlSession.class
                .getResourceAsStream("/db.properties");
    Properties prop = new Properties();
    prop.load(isRef);

    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream, prop);
    SqlSession sqlSession = sqlSessionFactory.openSession();

在SqlSessionFactoryBuilder类的build()方法中将配置inputStream流与属性对象prop进行关联,就可以根据${username}表达式读取properties属性文件中的同名key对应的值了,从而成功读取连接数据库的4个必要参数。

本章节使用Java代码的方式来传入4个必要变量,此种用法可以保护properties属性文件中的内容具有十足的安全性,比如可以在properties属性文件中保存经过加密后的password值,然后在Java代码中对properties属性文件中的password值再进行进行解密即可,示例伪代码如下。

            Properties prop = new Properties();
            prop.setProperty("driver", 解密的方法(prop.getProperty("driver")));
            prop.setProperty("url", 解密的方法(prop.getProperty("url")));
            prop.setProperty("username", 解密的方法(prop.getProperty("username")));
            prop.setProperty("password", 解密的方法(prop.getProperty("password")));

本实验的源代码在项目properties_encode_decode中。

完整示例代码如下。

public class SelectAll {

    // 加密算法
    public static String encode(String password) {
        return password = "_" + password;
    }

    // 解密算法
    public static String decode(String password) {
        return password.substring(1);
    }

    public static void main(String[] args) throws IOException {
        String configFile = "mybatis-config.xml";
        String dbInfoFile = "dbinfo.properties";

        InputStream configStream = Resources.getResourceAsStream(configFile);
        InputStream dbinfoStream = Resources.getResourceAsStream(dbInfoFile);

        Properties prop = new Properties();
        prop.load(dbinfoStream);
        String password = decode(prop.getProperty("password"));
        prop.setProperty("password", password);

        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream, prop);
        SqlSession session = factory.openSession();
        UserinfoMapping mapping = session.getMapper(UserinfoMapping.class);
        System.out.println("数据库信息来自于properties文件!");

        List<Userinfo> listUserinfo = mapping.getAllUserinfo();
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo eachUserinfo = listUserinfo.get(i);
            System.out.println(eachUserinfo.getId() + " " + eachUserinfo.getUsername() + " "
                    + eachUserinfo.getPassword() + " " + eachUserinfo.getAge() + " " + eachUserinfo.getInsertdate());
        }
        session.commit();
        session.close();
    }

}

如果不要求properties属性文件信息的安全性,还可以在mybatis-config.xml配置文件中直接引用指定的properties属性文件,再根据固定的key找到对应的value,也能达到连接数据库的目的,此种方法不需要使用Java代码来操作properties属性文件。

创建名称为propertiesSaveDBInfo的项目,添加dbinfo.properties文件,代码如下。

driver=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=y2
password=123

配置文件mybatis-config.xml的代码如下。

<configuration>
    <properties resource="dbinfo.properties"></properties>
    <typeAliases>
        <typeAlias alias="userinfo" type="sqlmapping.Userinfo" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

添加了配置代码<properties resource="dbinfo.properties"></properties>来使用dbinfo.properties文件中的连接数据库信息。

运行类代码如下。

public class GetAllUserinfo {

    public static void main(String[] args) {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            List<Userinfo> listUserinfo = userinfoMapper.getAllUserinfo();
            for (int i = 0; i < listUserinfo.size(); i++) {
                Userinfo userinfo = listUserinfo.get(i);
                System.out.println(userinfo.getId() + " " + userinfo.getUsername() + " " + userinfo.getPassword() + " "
                        + userinfo.getAge());
            }

            sqlSession.commit();
            sqlSession.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

成功将数据表中的数据查询出来。

数据源DataSource可以提升操作数据库的速度,加快程序运行的效率。本节将介绍MyBatis与DataSource常见的联合使用方法。

可以在mybatis-config.xml配置文件中创建多个数据源,方便切换欲操作的数据库。

创建名称为moreEnvironment的项目,并创建dbinfo.properties属性文件,内容如下。

oracle1.driver=oracle.jdbc.OracleDriver
oracle1.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle1.username=y2
oracle1.password=123

oracle2.driver=oracle.jdbc.OracleDriver
oracle2.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle2.username=y2
oracle2.password=errorPassword_errorPassword_###########

从属性文件中提供的内容可以发现是2个Oracle数据库的连接信息,其中第2个Oracle的连接密码是错误的。

配置文件mybatis-config.xml代码如下。

<?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>
    <properties resource="dbinfo.properties"></properties>
    <typeAliases>
        <typeAlias alias="userinfo" type="sqlmapping.Userinfo" />
    </typeAliases>
    <environments default="oracle2">
        <environment id="oracle1">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${oracle1.driver}" />
                <property name="url" value="${oracle1.url}" />
                <property name="username" value="${oracle1.username}" />
                <property name="password" value="${oracle1.password}" />
            </dataSource>
        </environment>
        <environment id="oracle2">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${oracle2.driver}" />
                <property name="url" value="${oracle2.url}" />
                <property name="username" value="${oracle2.username}" />
                <property name="password" value="${oracle2.password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

配置<environments default="oracle2">代码中的default属性值是oracle2,代表要连接的是<environment id="oracle2">中的配置信息,也就是在配置代码<environments default="oracle2">中来切换欲操作数据库的实例,从而达到配置多数据源的目的。

什么是JNDI?这里暂时先不介绍JNDI的理论概念。在通常情况下,常用对象的组织结构是树形结构,如图2-3所示。

图2-3 JNDI树结构

为了更好地组织多个对象,可以把多个对象绑定到一个树中,使用结点名称对应结点值,来找到目标对象。这非常类似于Map结构,这个树称为“JNDI树”。

如果每个厂商都不使用树形结构来组织数据,也就是说每个厂商对对象的组织方式都不一样,有的放入List,有的放入Map,有的创建自己的数据结构来保存对象,那么程序员需要掌握以上3种API来获取每个厂商提供的对象。

MyList.get(0)
MyMap.get(key)
自定义的数据结构.getObject()

这样的写法大大增加了程序员学习的成本,并且使得程序代码的移植性非常得不便。为了达到获取对象的API命名的统一性,Sun发布了JNDI规范,让这些厂商遵循JNDI规范。使用了JNDI规范后,不管使用什么样的方式保存对象,程序员只需要使用1种方式获得对象,就可以在不同厂商的不同数据结构中获得指定的对象值,这个特点和使用1 种JDBC接口就可以操作不同数据库的原理是一样的。

接口DataSource的主要作用就是获得Connection连接,是为了制定在不同容器产品中获得Connection的API进行规范化,而接口JNDI的主要作用就是制定在不同容器产品中操作JNDI树中的数据的API的规范化。简单总结:

下面来看一下在Tomcat容器中使用JNDI接口的API来操作JNDI树结点与对应值的代码吧。

创建Web项目jdbcJNDITest。

在JNDI树中绑定值的代码如下。

package jnditest;

import java.io.IOException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class test1 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            context.bind("username", "中国人");
            context.close();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

从JNDI树中取得值的代码如下。

public class test2 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            String value = (String) context.lookup("username");
            context.close();
            System.out.println(value);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

结点值还可以是父子关系,代码如下。

public class test3 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            context.bind("username.password", "中国人密码");
            context.close();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

上面的代码可以在Tomcat中进行运行,并且是没有错误的,但在weblogic中以这样的代码方式创建父子结点就会出现错误,正确地在weblogic中创建父子结点的代码如下:

try {
    Context context = new InitialContext();
    context = context.createSubcontext("a");
    context = context.createSubcontext("b");
    context.bind("c", "cValue");
    context.close();
} catch (NamingException e) {
    e.printStackTrace();
}

取得节点是父子关系对应值的代码如下。

public class test4 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            String value = (String) context.lookup("username.password");
            context.close();
            System.out.println(value);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

删除结点的代码如下。

public class test5 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            context.unbind("username");
            context.close();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

更新JNDI结点值的代码如下。

public class test6 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            Context context = new InitialContext();
            context.rebind("username", "新中国人");
            context.close();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

上面的代码就是在Tomcat中操作JNDI树结点与对应的值,这些代码可以在不更改任何一行的情况下成功在其他容器中移植运行。这就是JNDI规范的优点:API统一,代码移植性好。

使用JNDI规范中的API就可以从JNDI树中找到对应的对象。大多数Web容器都支持JNDI规范,因为JNDI规范属于Java EE技术体系之一。有了JNDI规范后,只要Web容器支持JNDI规范,那么在获得对象时使用的代码是一样的。

在Web开发中,如果使用Tomcat做为Web容器,则Tomcat内部默认提供了获得Connection连接对象的数据源DataSource,可以使用相应的API来从DataSource中取得连接Connection,并操作数据库。

数据源DataSource接口的信息如图2-4所示。

图2-4 数据源DataSource接口的信息

数据源DataSource接口在Java Doc中官方解释如下。

该工厂用于提供到此DataSource对象所表示的物理数据源的连接。作为DriverManager工具的替代项,DataSource对象是获取连接Connection的首选方法。实现DataSource接口的对象通常在基于JavaTM Naming and Directory Interface (JNDI) API的命名服务中注册。

以上文字可以总结出3条:

大多数JavaWeb容器内部的DataSource数据源提供的Connection都已经被池化,所谓的“池化”也就是预先在Tomcat中创建一个空间,在这个空间中存储一些已经连接到数据库的Connection连接,客户端在使用Connection时只需要get获取即可,省略了每次连接数据库时的IP寻址、username和password验证等重复的环节,大大提高软件的运行效率,这个空间就是Connection Pool连接池。

那么JNDI与DataSource数据源以及ConnectionPool连接池之间到底是什么关系呢?效果如图2-5所示。

图2-5 三者之间的关系

在Tomcat中获得数据源DataSource之前要在Tomcat的conf文件夹中的context.xml文件里配置DataSource数据源信息,Tomcat启动时会读取这些信息,然后将DataSource对象放到JNDI树结点上,核心代码如下。

<Resource name="jdbc/myOracle" type="javax.sql.DataSource"
    driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@localhost:1521:orcl"
    username="y2" password="123" maxActive="200" maxIdle="10" maxWait="-1" />

<Resource>中的name值“jdbc/myOracle”就是JNDI树节点的名称,对应的值就是数据源DataSource。

然后在tomcat的conf文件夹中的web.xml文件里引用数据源,核心代码如下。

    <resource-ref>
        <res-ref-name>jdbc/myOracle</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
</resource-ref>

先来看看若不使用MyBatis如何从数据源DataSource中获得Connection。继续在jdbcJNDITest项目中进行测试,创建Servlet的代码如下。

public class test7 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            Context context = new InitialContext();
            DataSource ds = (DataSource) context.lookup("java:/comp/env/jdbc/myOracle");
            Connection connection = ds.getConnection();
            ResultSet rs = connection.prepareStatement("select * from userinfo order by id asc").executeQuery();
            while (rs.next()) {
                System.out.println(rs.getString("id") + " " + rs.getString("username"));
            }
            rs.close();
            connection.close();
        } catch (NamingException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

执行此Servlet后,在tomcat的JDNI树上找到DataSource数据源对象,然后通过此对象获得Connection连接,进而操作数据库。

JNDI以统一的数据组织方式“JNDI树”来组织数据,以统一的方式对数据进行存取,比如存储数据要使用bind()方法,而取得数据要使用lookup()方法。它是访问数据,共享数据的一种Java EE规范。

MyBatis中使用DataSource数据源的实验在项目JNDIDataSourceTest中进行测试,创建配置文件mybatis-config.xml的代码如下。

<?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>
    <properties resource="dbinfo.properties">
    </properties>
    <environments default="jndi1">
        <environment id="jndi1">
            <transactionManager type="JDBC" />
            <dataSource type="JNDI">
                <property name="data_source" value="${jndiName}" />
            </dataSource>
        </environment>
        <environment id="jndi2">
            <transactionManager type="JDBC" />
            <dataSource type="JNDI">
                <property name="data_source" value="java:/comp/env/jdbc/myOracle" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapping/UserinfoMapper.xml" />
    </mappers>
</configuration>

其中配置代码<transactionManager type="JDBC" />的主要作用就是程序员需要使用显式代码进行事务的提交,也就是程序员必须要调用SqlSession对象的commit()方法才会更改数据库。

还有另外一个写法。

<transactionManager type="MANAGED" />

此写法的作用是每执行1条SQL语句后,事务进行自动提交。

创建Servlet类Test1.java代码如下。

package controller;

import java.io.IOException;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.ibatis.session.SqlSession;

import dbtools.GetSqlSession;
import entity.Userinfo;
import mapping.UserinfoMapper;

public class test1 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Userinfo userinfo1 = new Userinfo();
        userinfo1.setUsername("中国1");
        userinfo1.setPassword("中国人1");
        userinfo1.setAge(100L);
        userinfo1.setInsertdate(new Date());

        Userinfo userinfo2 = new Userinfo();
        userinfo2.setUsername("中国2");
        userinfo2.setPassword("中国人2");
        userinfo2.setAge(100L);
        userinfo2.setInsertdate(new Date());

        SqlSession session = GetSqlSession.getSqlSession();
        UserinfoMapper mapper = session.getMapper(UserinfoMapper.class);
        mapper.insertUserinfo(userinfo1);
        mapper.insertUserinfo(userinfo2);
        // 此案例没有提交
        // session.commit();
        session.close();
    }

}

程序运行后在数据表userinfo中并没有添加任何一条记录,因为事务使用的是<transactionManager type="JDBC" />显式处理。

更改配置文件mybatis-config.xml的代码如下。

    <environments default="jndi1">
        <environment id="jndi1">
            <transactionManager type="MANAGED" />
            <dataSource type="JNDI">
                <property name="data_source" value="${jndiName}" />
            </dataSource>
        </environment>

再次执行Test1.java类,程序执行完毕后在数据表userinfo中添加了2条记录,每执行1条SQL语句后事务进行了自动提交。

以上测试的结果说明,在程序代码没有错误的情况下,配置代码<transactionManager type="MANAGED" />和<transactionManager type="JDBC" />的主要区别就是事务是否自动提交。

下面再来看一下在程序代码出现错误的情况下,这两者是如何处理事务的。

首先要将userinfo数据表中的全部记录删除。

更改配置文件mybatis-config.xml的代码如下。

    <environments default="jndi1">
        <environment id="jndi1">
            <transactionManager type="JDBC" />
            <dataSource type="JNDI">
                <property name="data_source" value="${jndiName}" />
            </dataSource>
        </environment>

创建Servlet类Test2.java的代码如下。

public class test2 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Userinfo userinfo1 = new Userinfo();
        userinfo1.setUsername("中国1");
        userinfo1.setPassword("中国人1");
        userinfo1.setAge(100L);
        userinfo1.setInsertdate(new Date());

        Userinfo userinfo2 = new Userinfo();
        userinfo2.setUsername("中国2");
        userinfo2.setPassword(
                "中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2中国人2");
        userinfo2.setAge(100L);
        userinfo2.setInsertdate(new Date());

        SqlSession session = GetSqlSession.getSqlSession();
        UserinfoMapper mapper = session.getMapper(UserinfoMapper.class);
        mapper.insertUserinfo(userinfo1);
        mapper.insertUserinfo(userinfo2);
        // 此案例没有提交
        // session.commit();
        session.close();
    }

}

程序运行后,数据表userinfo中并没有添加任何一条记录,程序代码出现异常导致事务整体回滚,因为配置代码<transactionManager type="JDBC" />会使用autocommit值为false的配置,而配置代码<transactionManager type="MANAGED" />会使用autocommit值为true的配置。

继续更改配置文件mybatis-config.xml,代码如下。

    <environments default="jndi1">
        <environment id="jndi1">
            <transactionManager type="MANAGED" />
            <dataSource type="JNDI">
                <property name="data_source" value="${jndiName}" />
            </dataSource>
        </environment>

再次执行名称为Test2.java的Servlet,程序执行完毕后,数据表userinfo中只出现1条记录,因为第2个insert操作失败。由于<transactionManager type="MANAGED" />的配置是每执行一个SQL就提交事务,所以第2条insert语句由于出现异常并没有成功添加到userinfo数据表中。

从JNDI树中根据JNDI结点名称name取得对应的DataSource数据源value值,再从DataSource中取得Connection连接对象。以上就是在Web环境中使用JNDI的过程,JNDI的代码是需要程序员自己输入,而MyBatis封装了这个过程。

在第1章中使用MyBatis时,使用配置<dataSource type="POOLED">在MyBatis中创建ConnectionPool连接池,这个连接池是MyBatis自己创建的,在效率上得到了保障。MyBatis还支持第三方的连接池,第三方的连接池在获得Connection时会更加高效,本章就将现阶段主流的HikariCP连接池与MyBatis进行整合。

创建名称为use_HikariDataSource的项目,类MyDataSourceFactory.java的代码如下。

package mysourcefactory;

import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceFactory;
import com.zaxxer.hikari.HikariDataSource;

public class MyDataSourceFactory implements DataSourceFactory {

    Properties prop;

    @Override
    public DataSource getDataSource() {
        System.out.println("2 public DataSource getDataSource()");
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName(prop.getProperty("driver"));
        ds.setJdbcUrl(prop.getProperty("url"));
        ds.setUsername(prop.getProperty("username"));
        ds.setPassword(prop.getProperty("password"));
        ds.setMinimumIdle(10);
        ds.setMaximumPoolSize(10);
        return ds;
    }

    @Override
    public void setProperties(Properties arg0) {
        System.out.println("1 public void setProperties(Properties arg0) " + arg0.size());
        prop = arg0;
    }

}

配置文件mybatis-config.xml的代码如下。

<?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>
    <properties resource="dbinfo.properties"></properties>
    <typeAliases>
        <package name="entity" />
    </typeAliases>
    <environments default="jdbc2">
        <environment id="jdbc1">
            <transactionManager type="JDBC" />
            <dataSource type="mysourcefactory.MyDataSourceFactory">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
        <environment id="jdbc2">
            <transactionManager type="JDBC" />
            <dataSource type="mysourcefactory.MyDataSourceFactory">
                <property name="driver" value="oracle.jdbc.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapping/userinfoMapping.xml" />
    </mappers>
</configuration>

运行类的代码如下。

public class SelectAll {

    public static void main(String[] args) throws IOException, SQLException {
        String configFile = "mybatis-config.xml";

        InputStream configStream = Resources.getResourceAsStream(configFile);

        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        UserinfoMapping mapping = session.getMapper(UserinfoMapping.class);
        System.out.println("z13");

        List<Userinfo> listUserinfo = mapping.getAllUserinfo();
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo eachUserinfo = listUserinfo.get(i);
            System.out.println(eachUserinfo.getId() + " " + eachUserinfo.getUsername() + " "
                    + eachUserinfo.getPassword() + " " + eachUserinfo.getAge() + " " + eachUserinfo.getInsertdate());
        }
        System.out.println();
        System.out.println();
        DataSource dataSource =   
factory.getConfiguration().getEnvironment().getDataSource();
        for (int i = 0; i < 15; i++) {
            // 注意,不要打印connection对象的hashCode()值
            // 因为现在的Connection接口的实现类并不是:
            // oracle.jdbc.driver.T4CConnection
            // 而是HikariProxyConnection
            // 多个HikariProxyConnection对象使用的是同一个oracle.jdbc.driver.T4CConnection对象
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        }
        session.commit();
        session.close();
    }

}

程序运行后控制台输出信息如下。

1 public void setProperties(Properties arg0) 4
2 public DataSource getDataSource()
1542469 中国 大中国 100 null
1542470 中国 大中国 100 null
1542471 中国 中国人 100 Wed Nov 02 09:26:44 CST 2016
1542472 中国 中国人 100 Wed Nov 02 09:31:15 CST 2016
1542476 x xx 200 Mon Nov 07 09:39:37 CST 2016
1542477 a 我是中文密码! 100 Mon Nov 07 09:43:22 CST 2016
1542478 a 我是中文密码! 100 Mon Nov 07 09:50:06 CST 2016

HikariProxyConnection@1543148593 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@2028555727 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@591391158 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@898557489 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@247944893 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@1014166943 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@1625082366 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@572593338 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@384294141 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@1024597427 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@990355670 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@296347592 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@956420404 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@349420578 wrapping oracle.jdbc.driver.T4CConnection@3db346a8
HikariProxyConnection@315932542 wrapping oracle.jdbc.driver.T4CConnection@3db346a8

从控制台打印的信息来看,使用的是同1个Connection连接。

在设计软件系统时经常需要考虑多数据库的支持,也就是在Java代码与SQL映射代码都不变的情况下来执行不同的SQL语句,MyBatis新版是支持这个功能的。

创建名称为databaseIdTest的项目,SQL映射文件UserinfoMapper.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="sqlmapping.UserinfoMapper">
    <select id="getAllUserinfo" resultType="userinfo" databaseId="xxxOracle">
        select * from
        userinfo order by id asc
    </select>
    <select id="getAllUserinfo" resultType="userinfo" databaseId="yyyMYSQL">
        select * from
        userinfo order by id desc
    </select>
</mapper>

配置文件mybatis-config.xml的代码如下。

<?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>
        <typeAlias alias="userinfo" type="sqlmapping.Userinfo" />
    </typeAliases>
    <environments default="mysql">
        <environment id="oracle">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="y2" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
        <environment id="mysql">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/y2" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <databaseIdProvider type="DB_VENDOR">
        <property name="Oracle" value="xxxOracle" />
        <property name="MySQL" value="yyyMYSQL" />
    </databaseIdProvider>
    <mappers>
        <mapper resource="sqlmapping/UserinfoMapper.xml" />
    </mappers>

</configuration>

配置代码<property name="Oracle" value="xxxOracle" />中的name属性值是通过代码获得的:

        System.out.println("getDatabaseProductName()=" + factory.getConfiguration().getEnvironment().getDataSource()
                .getConnection().getMetaData().getDatabaseProductName());

属性name的值不能随意填写,并且还要注意大小写,因为是区分大小写的。属性name代表数据库的产品名称,而value属性代表这个数据库产品名称的别名。value的属性可以随意命名,但尽量命名的有意义。然后在SQL映射文件中使用如下代码。

    <select id="getAllUserinfo" resultType="userinfo" databaseId="xxxOracle">
        select * from
        userinfo order by id asc
    </select>
    <select id="getAllUserinfo" resultType="userinfo" databaseId="yyyMYSQL">
        select * from
        userinfo order by id desc
    </select>

在不同的SQL映射上引用不同的数据库别名xxxOracle和yyyMYSQL,这样就可以达到和SQL映射的id值一样,但在不同的数据库中可以执行不同的SQL语句的目的了。

使用不同的数据源可以执行不同的SQL语句,Oracle是正序,而MySQL是倒序。

创建名称为moreMapperTest的项目。

在mybatis-config.xml配置文件中可以使用4种方式来获取Mapper映射,示例代码如下。

        <!-- 第1种写法: 直接获取SQL映射xml文件-->
        <mapper resource="sqlmapping/UserinfoMapper.xml" />

        <!-- 第2种写法: 扫描包,注意接口的名称和xml的文件的主文件名必须一样-->
        <package name="sqlmapping" />

        <!-- 第3种写法: 指定接口名称,注意接口的名称和xml的文件的主文件名必须一样-->
        <mapper class="sqlmapping.UserinfoMapper" />

        <!-- 第4种写法: 使用绝对路径-->
        <mapper
            url="file:\\\C:\Users\gaohongyan\workspace\moreMapperTest\src\sqlmapping\UserinfoMapper.xml" />

这4种方式建议不要混用。

MyBatis框架是基于SQL映射的,所以SQL映射文件在此框架中的位置非常重要。动态SQL是MyBatis提供的,它根据指定的条件来执行指定的SQL语句,它使SQL映射文件中的SQL语句在执行时具有动态性。SQL映射文件与动态SQL被设计得非常简单。本节将和大家一起学习SQL映射文件中的常用实例的使用。

如果数据表中字段的名称和Java实体类中属性的名称不一致,就要使用<resultMap>标签来做一个映射。

创建名称为resultMapTest的Java项目,映射配置文件userinfoMapping.xml的代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">

    <resultMap type="entity.Userinfo" id="userinfo">
        <result column="id" property="idghy" />
        <result column="username" property="usernameghy" />
        <result column="password" property="passwordghy" />
        <result column="age" property="ageghy" />
        <result column="insertdate" property="insertdateghy" />
    </resultMap>

    <select id="getUserinfoAll" resultMap="userinfo">
        select * from
        userinfo
    </select>
</mapper>

在<select>标签中使用resultMap属性来引用<resultMap>的id属性值,形成映射关系。

实体类Userinfo.java的结构如图2-6所示。

图2-6 类Userinfo.java结构

创建Run.java运行类,核心代码如下。

public class Run {

    public static void main(String[] args) {

        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            List<Userinfo> listUserinfo = sqlSession
                    .selectList("getUserinfoAll");
            for (int i = 0; i < listUserinfo.size(); i++) {
                Userinfo userinfo = listUserinfo.get(i);
                System.out.println(userinfo.getIdghy() + " "
                        + userinfo.getUsernameghy() + " "
                        + userinfo.getPasswordghy() + " "
                        + userinfo.getAgeghy() + " "
                        + userinfo.getInsertdateghy());
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

程序运行后的结果如图2-7所示。

图2-7 成功输出数据表中的数据

重复的sql语句永远不可避免,<sql>标签就是用来解决这个问题的。

创建名称为sqlTest的Java项目,映射配置文件userinfoMapping.xml的代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">

    <sql id="userinfoField">id,username,password,age,insertdate</sql>

    <select id="getUserinfoAll" resultType="map">
        select
        <include refid="userinfoField" />
        from
        userinfo
    </select>

    <select id="getUserinfoById" resultType="map" parameterType="int">
        select
        <include refid="userinfoField" />
        from
        userinfo where id=#{0}
    </select>
</mapper>

上面代码中的id、username、password、age和insertdate这5个字段在映射文件中多处出现,所以可以将这5个字段封装进<sql>标签中,以减少配置的代码量。

创建Run.java运行测试类,代码如下。

public class Run {

    public static void main(String[] args) {

        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            List<Map> listUserinfo = sqlSession.selectList("getUserinfoAll");
            for (int i = 0; i < listUserinfo.size(); i++) {
                Map map = listUserinfo.get(i);
                System.out.println(map.get("ID") + " " + map.get("USERNAME")
                        + " " + map.get("PASSWORD") + " " + map.get("AGE")
                        + " " + map.get("INSERTDATE"));
            }
            System.out.println("");
            System.out.println("");
            listUserinfo = sqlSession.selectList("getUserinfoById", 5);
            for (int i = 0; i < listUserinfo.size(); i++) {
                Map map = listUserinfo.get(i);
                System.out.println(map.get("ID") + " " + map.get("USERNAME")
                        + " " + map.get("PASSWORD") + " " + map.get("AGE")
                        + " " + map.get("INSERTDATE"));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

程序运行后的结果如图2-8所示。

图2-8 程序运行结果

本例的sql映射文件是从map里使用get(字段名称)的形式取得字段对应的值,但字段名称在Oracle中是大写字母,所以这里的字段名称也必须要写成大写形式。为了支持方便的小写形式,可以在映射文件中定义的SQL语句中为字段另起一个别名即可:

select id "id",username "username",password "password",age "age"

还可以使用<sql>标签来进行声明,示例代码如下:

    "userinfo4ColName">id "id",username "username",password "password",age "age"    

这样从map中就可以以小写的形式取得字段值了。

在MyBatis中也支持将SQL语句当成变量传入。

新建名称为sqlStringVar的Java项目,映射文件userinfoMapping.xml的内容如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">

    <select id="getUserinfo" parameterType="map" resultType="map">
        select
        id,username,password,age,insertdate
        from
        userinfo where id>#{id} order
        by ${orderSql}
    </select>

</mapper>

Java类文件Run.java的代码如下。

public class Run {

    public static void main(String[] args) {

        try {
            HashMap mapParam = new HashMap();
            mapParam.put("id", 5);
            mapParam.put("orderSql", "id desc");

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            List<Map> listUserinfo = sqlSession.selectList("getUserinfo",
                    mapParam);
            for (int i = 0; i < listUserinfo.size(); i++) {
                Map map = listUserinfo.get(i);
                System.out.println(map.get("ID") + " " + map.get("USERNAME")
                        + " " + map.get("PASSWORD") + " " + map.get("AGE")
                        + " " + map.get("INSERTDATE"));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

运行结果如图2-9所示。

图2-9 运行结果

如果在SQL语句中出现 < 小于或者是 <= 小于等于的条件时,由于 < 符号是特殊的符号,所以要使用<![CDATA[]]>将其转化成为普通的字符串,而不是XML标记的左开始标签,示例代码如下。

    <select id="getAllUserinfo" parameterType="map" resultType="map">
        select
        <include refid="userinfo4ColName"></include>
        from userinfo where id
        <![CDATA[<=]]>
        #{id}
        order by ${orderSQL}
    </select>

创建名称为dynSqlTest的Web项目,创建名称为test1的Servlet对象,核心代码如下。

public class test1 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("中国");
        userinfo.setPassword(null);
        userinfo.setAge(200L);
        userinfo.setInsertdate(new Date());

        SqlSession sqlSessionRef = GetSqlSession.getSqlSession();
        sqlSessionRef.insert("insertUserinfo", userinfo);
        sqlSessionRef.commit();
        sqlSessionRef.close();

    }

}

在映射文件userinfoMapping.xml中添加如下配置。

    <insert id="insertUserinfo" parameterType="orm.Userinfo">
        <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
            select idauto.nextval from dual
        </selectKey>
        insert into userinfo(id,username,password,age,insertdate)
        values(#{id},#{username},#{password},#{age},#{insertdate})
    </insert>

程序运行后出现如下异常信息。

### Error updating database.  Cause: org.apache.ibatis.type.TypeException: Error setting null for parameter #3 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 无效的列类型: 1111

从出错信息中可以看到,是用null值对password字段进行了设置,造成MyBatis无法识别出应该插入默认的数据类型值,这种情况可以通过设置映射的默认数据类型来解决。更改映射配置userinfoMapping.xml代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
<mapper namespace="mybatis.testcurd">
    <insert id="insertUserinfo" parameterType="orm.Userinfo">
        <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
            select idauto.nextval from dual
        </selectKey>
        insert into userinfo(id,username,password,age,insertdate)
        values(#{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{insertdate,jdbcType=TIMESTAMP})
    </insert>
</mapper>

这里在#{}格式中加入了数据类型的声明,这样可以明确地告诉MyBatis框架遇到null值该如何处理。再次运行程序,成功插入数据表,运行结果如图2-10所示。

图2-10 成功插入数据表

通过在#{}格式中加入jdbcType即可避免插入null值时的异常,其实使用动态SQL标签也可以达到同样的效果。

继续在dynSqlTest项目中进行添加代码。

在映射文件userinfoMapping.xml添加如下代码。

    <insert id="insertUserinfo2" parameterType="orm.Userinfo">
        <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
            select idauto.nextval from dual
        </selectKey>
        <if test="password!=null">
            insert into userinfo(id,username,password,age,insertdate)
            values(#{id},#{username},#{password},#{age},#{insertdate})
        </if>
        <if test="password==null">
            insert into userinfo(id,username,age,insertdate)
            values(#{id},#{username},#{age},#{insertdate})
        </if>
    </insert>

创建名称为test2的Servlet,核心代码如下。

public class test2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Userinfo userinfo1 = new Userinfo();
        userinfo1.setUsername("英国");
        userinfo1.setPassword(null);
        userinfo1.setAge(200L);
        userinfo1.setInsertdate(new Date());

        Userinfo userinfo2 = new Userinfo();
        userinfo2.setUsername("法国");
        userinfo2.setPassword("法国人");
        userinfo2.setAge(200L);
        userinfo2.setInsertdate(new Date());

        SqlSession sqlSessionRef = GetSqlSession.getSqlSession();
        sqlSessionRef.insert("insertUserinfo2", userinfo1);
        sqlSessionRef.insert("insertUserinfo2", userinfo2);
        sqlSessionRef.commit();
        sqlSessionRef.close();
    }

}

程序运行后,成功向数据表中插入2条记录,如图2-11所示。

图2-11 成功插入2条记录

标签<if>在判断字符串为空时还可以更加具体,示例代码如下。

<if test="password!=null and password!=’’">

也就是null值与""空字符串一同进行判断。

标签where的主要作用就是生成根据条件查询的SQL语句,可以用在insert、delete、update以及select语句中。

创建测试用的项目whereTAG,创建映射文件userinfoMapping.xml的代码如下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"mybatis-3-mapper.dtd">
<mapper namespace="test">
    <select id="selectA" resultType="map">
        select * from userinfo
        <where>
            <if test="username!=null">username like #{username}</if>
            <if test="age!=null">or age = #{age}</if>
        </where>
    </select>
</mapper>

运行类Test1.java的代码如下。

package controller;

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

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Test1 {

    public static void main(String[] args) {
        try {

            Map queryMap = new HashMap();
            queryMap.put("username", "%美%");
            queryMap.put("age", "999");

            InputStream configStream = Resources
                    .getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder()
                    .build(configStream);
            SqlSession session = factory.openSession();

            List<Map> listMap = session.selectList("selectA", queryMap);
            for (int i = 0; i < listMap.size(); i++) {
                Map eachMap = listMap.get(i);
                System.out.println(eachMap.get("ID") + " "
                        + eachMap.get("USERNAME") + " "
                        + eachMap.get("PASSWORD") + " " + eachMap.get("AGE"));
            }
            session.commit();
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

程序运行结果如图2-12所示。

图2-12 根据where条件查询的结果

如果在代码中不向username传递值,<where>标签也能自动去掉or关键字而成功执行单独查询age年龄的SQL语句。

<choose>标签的作用是在众多的条件中选择出一个条件,有些类似于Java语言中的switch语句的作用。

在映射文件userinfoMapping.xml中添加如下配置代码。

    <select id="selectUserinfo1" parameterType="orm.Userinfo"
        resultType="map">
        select * from userinfo where 1=1
        <choose>
            <when test="username!=null">and username like '%'||#{username}||'%'</when>
            <when test="password!=null">and password like '%'||#{password}||'%'</when>
            <otherwise>and age=200</otherwise>
        </choose>
    </select>

配置代码<when test="username!=null">中的username是指传入的参数Userinfo实体类中的username属性值是否为null。

创建名称为test3的Servlet对象,核心代码如下。

public class test3 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Userinfo userinfo1 = new Userinfo();
        userinfo1.setUsername("英");

        Userinfo userinfo2 = new Userinfo();
        userinfo2.setPassword("法");

        Userinfo userinfo3 = new Userinfo();

        SqlSession sqlSessionRef = GetSqlSession.getSqlSession();
        List<Map> listMap1 = sqlSessionRef.selectList("selectUserinfo1",
                userinfo1);
        for (int i = 0; i < listMap1.size(); i++) {
            Map eachMap = listMap1.get(i);
            System.out.println("listMap1中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }

        List<Map> listMap2 = sqlSessionRef.selectList("selectUserinfo1",
                userinfo2);
        for (int i = 0; i < listMap2.size(); i++) {
            Map eachMap = listMap2.get(i);
            System.out.println("listMap2中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }

        List<Map> listMap3 = sqlSessionRef.selectList("selectUserinfo1",
                userinfo3);
        for (int i = 0; i < listMap3.size(); i++) {
            Map eachMap = listMap3.get(i);
            System.out.println("listMap3中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }
        sqlSessionRef.commit();
        sqlSessionRef.close();

    }
}

数据表userinfo中的内容如图2-13所示。

图2-13 userinfo数据表内容

程序运行后,在控制台输出如图2-14所示的信息。

图2-14 运行结果

标签<choose>起到了和switch结合break语句一样的作用。

<set>标签可以用在update语句中,作用是动态指定要更新的列。

在映射文件userinfoMapping.xml中添加如下映射代码。

    <update id="updateUserinfo" parameterType="orm.Userinfo">
        update userinfo
        <set>
            <if test="username!=null">username=#{username},</if>
            <if test="password!=null">password=#{password},</if>
            <if test="age!=null">age=#{age},</if>
            <if test="insertdate!=null">insertdate=#{insertdate}</if>
        </set>
        <if test="id!=null">where id=#{id}</if>
    </update>

创建名称为test4的Servlet对象,核心代码如下。

public class test4 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Userinfo userinfo = new Userinfo();
        userinfo.setId(199L);
        userinfo.setUsername(null);
        userinfo.setPassword("新密码");
        userinfo.setAge(1000L);
        userinfo.setInsertdate(new Date());

        SqlSession sqlSessionRef = GetSqlSession.getSqlSession();
        sqlSessionRef.update("updateUserinfo", userinfo);
        sqlSessionRef.commit();
        sqlSessionRef.close();
    }

}

程序运行结果如图2-15所示。

图2-15 成功更新

<foreach>标签有循环的功能,可以用来生成有规律的SQL语句。

<foreach>标签主要的属性有item、index、collection、open、separator和close。

item表示集合中每一个元素进行迭代时的别名,index指定一个名字,用于表示在迭代过程中每次迭代到的位置,open表示该语句以什么开始,separator表示在每次迭代之间以什么符号作为分隔符,close表示该语句以什么结束。

创建新的Web项目foreachTest。

在映射文件userinfoMapping.xml中添加如下配置代码。

    <select id="selectUserinfo2" parameterType="list" resultType="map">
        select * from userinfo where id in
        <foreach collection="list" item="eachId" index="currentIndex"
            open="(" separator="," close=")">
            #{eachId}
            </foreach>
    </select>

    <select id="selectUserinfo3" parameterType="orm.QueryUserinfo"
        resultType="map">
        select * from userinfo where id in
        <foreach collection="idList" item="eachId" index="currentIndex"
            open="(" separator="," close=")">
            #{eachId}
        </foreach>
        and username like '%'||#{username}||'%'
    </select>

    <select id="selectUserinfo4" parameterType="map" resultType="map">
        select * from userinfo where id in
        <foreach collection="idList" item="eachId" index="currentIndex"
            open="(" separator="," close=")">
            #{eachId}
        </foreach>
        and username like '%'||#{username}||'%'
    </select>

在Oracle数据库中,字符串的拼接要使用||符号。

创建名称为test5的Servlet,核心代码如下。

public class test5 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // id的值来自于List
        List list = new ArrayList();
        list.add(1);
        list.add(3);
        list.add(5);

        SqlSession sqlSessionRef = GetSqlSession.getSqlSession();
        List<Map> listMap1 = sqlSessionRef.selectList("selectUserinfo2", list);
        for (int i = 0; i < listMap1.size(); i++) {
            Map eachMap = listMap1.get(i);
            System.out.println("list1中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }

        // id的值来自于QueryUserinfo实体中的List
        QueryUserinfo queryUserinfo = new QueryUserinfo();
        queryUserinfo.setUsername("法");
        queryUserinfo.getIdList().add(198);
        queryUserinfo.getIdList().add(199);

        listMap1 = sqlSessionRef.selectList("selectUserinfo3", queryUserinfo);
        for (int i = 0; i < listMap1.size(); i++) {
            Map eachMap = listMap1.get(i);
            System.out.println("list2中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }

        // id的值来自于Map中的List
        Map paramMap = new HashMap();
        paramMap.put("username", "5");
        paramMap.put("idList", list);

        listMap1 = sqlSessionRef.selectList("selectUserinfo4", paramMap);
        for (int i = 0; i < listMap1.size(); i++) {
            Map eachMap = listMap1.get(i);
            System.out.println("list3中的内容:  " + eachMap.get("ID") + " "
                    + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD")
                    + " " + eachMap.get("AGE") + " "
                    + eachMap.get("INSERTDATE"));
        }

        sqlSessionRef.commit();
        sqlSessionRef.close();

    }

}

程序运行后,控制台输出结果如图2-16所示。

图2-16 控制台输出结果

如何实现批量insert插入操作呢?由于批量插入的SQL语句在每种数据库中都不一样,比如Oracle是使用如下格式的语句。

INSERT INTO userinfo (id,
                      username,
                      password,
                      age,
                      insertdate)
   SELECT idauto.NEXTVAL,
          username,
          password,
          age,
          insertdate
     FROM (SELECT 'a' username,
                  'aa' password,
                  1 age,
                  TO_DATE ('2000-1-1', 'yyyy-MM-dd') insertdate
             FROM DUAL
           UNION ALL
           SELECT 'b' username,
                  'bb' password,
                  1 age,
                  TO_DATE ('2000-1-1', 'yyyy-MM-dd') insertdate
             FROM DUAL)

而MySQL是使用如下格式的SQL语句来实现批量insert插入。

insert into userinfo(username,password,age,insertdate)
values('a','aa',1,'2000-1-1'),
('b','aa',1,'2000-1-1'),
('c','aa',1,'2000-1-1'),
('d','aa',1,'2000-1-1'),
('e','aa',1,'2000-1-1')

由于批量insert插入的SQL语句在每种数据库中都不一样,就导致在MyBatis中SQL映射文件的写法也不一样。

下面来看看针对Oracle数据库的SQL映射文件的代码,具体如下。

    <insert id="insertOracle" parameterType="list">
        INSERT INTO userinfo (id,
        username,
        password,
        age,
        insertdate)
        select
        idauto.nextval,username,password,age,insertdate from (
        <foreach collection="list" item="eachUserinfo" separator="union all">
            select
            #{eachUserinfo.username} username,#{eachUserinfo.password}
            password,#{eachUserinfo.age} age,#{eachUserinfo.insertdate}
            insertdate
            from dual
        </foreach>
        )
    </insert>

Java代码如下。

public class Insert1 {

    public static void main(String[] args) throws IOException {

        List list = new ArrayList();

        for (int i = 0; i < 5; i++) {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("中国" + (i + 1));
            userinfo.setPassword("中国人" + (i + 1));
            userinfo.setAge(100L);
            userinfo.setInsertdate(new Date());
            list.add(userinfo);
        }

        SqlSession session = GetSqlSession.getSqlSession();
        session.insert("insertOracle", list);
        session.commit();
        session.close();

    }

}

下面来看看针对MySQL数据库的SQL映射文件的代码,具体如下。

        <insert id="insertMySQL" parameterType="list">
        INSERT INTO userinfo (
        username,
        password,
        age,
        insertdate)
        values
        <foreach collection="list" item="eachUserinfo" separator=",">
            (
            #{eachUserinfo.username},#{eachUserinfo.password},
            #{eachUserinfo.age},#{eachUserinfo.insertdate}
            )
        </foreach>
    </insert>

Java代码如下。

public class Insert1 {

    public static void main(String[] args) throws IOException {

        List list = new ArrayList();

        for (int i = 0; i < 5; i++) {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("中国MYSQL" + (i + 1));
            userinfo.setPassword("中国人MYSQL" + (i + 1));
            userinfo.setAge(100L);
            userinfo.setInsertdate(new Date());
            list.add(userinfo);
        }

        SqlSession session = GetSqlSession.getSqlSession();
        session.insert("insertMySQL", list);
        session.commit();
        session.close();

    }

}

在SQL语句中使用like查询时,MySQL在拼接字符串时使用concat()方法,而Oracle却使用||运算符,两者的SQL语句并不相同,这就要创建2个SQL映射语句。下面创建实验用的项目mysql_oracle_2_sql,SQL映射文件示例代码如下。

<mapper namespace="userinfo">
    <select id="selectOracle" parameterType="string" resultType="entity.Userinfo">
        select * from userinfo where username like '%'||#{username}||'%'
    </select>
    <select id="selectMySQL" parameterType="string" resultType="entity.Userinfo">
        select * from userinfo where username like concat('%',#{username},'%')
    </select>
</mapper>

通过在运行代码中更改selectList()方法的第1个参数值来切换、执行不同的SQL语句,示例代码如下。

List<Userinfo> listUserinfo = session.selectList("selectOracle", "a");
List<Userinfo> listUserinfo = session.selectList("selectMySQL", "a");

这样会更改软件的源代码,不利于软件的稳定性,这时可以使用<bind>标签在多个数据库之间进行适配。

创建名称为bindTAGNew的项目,SQL映射文件代码如下。

<?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="userinfo">
    <select id="select1" parameterType="string" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+_parameter+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
    <select id="select2" parameterType="entity.Userinfo" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+_parameter.getUsername()+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
    <select id="select3" parameterType="entity.Userinfo" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+username+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
    <select id="select4" parameterType="entity.Userinfo" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+_parameter.username+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
    <select id="select5" parameterType="entity.Userinfo" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+#root._parameter.username+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
    <select id="select6" parameterType="map" resultType="entity.Userinfo">
        <bind name="myQuerySQL" value="'%'+username+'%'" />
        select * from userinfo
        where username like #{myQuerySQL}
    </select>
</mapper>

<bind>标签的value属性值中的运算是使用OGNL表达式,OGNL表达式的底层是使用Java语言,所以在Java语言中可以直接使用+号进行字符串的拼接,并不像在Oracle或MySQL中要使用||或concat()。

多个运行类代码如下。

public class Select1 {
    public static void main(String[] args) throws IOException {
        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select1", "a");
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}

public class Select2 {
    public static void main(String[] args) throws IOException {
        Userinfo queryUserinfo = new Userinfo();
        queryUserinfo.setUsername("a");

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select2", queryUserinfo);
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}


public class Select3 {
    public static void main(String[] args) throws IOException {
        Userinfo queryUserinfo = new Userinfo();
        queryUserinfo.setUsername("a");

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select3", queryUserinfo);
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}


public class Select4 {
    public static void main(String[] args) throws IOException {
        Userinfo queryUserinfo = new Userinfo();
        queryUserinfo.setUsername("a");

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select4", queryUserinfo);
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}


public class Select5 {
    public static void main(String[] args) throws IOException {
        Userinfo queryUserinfo = new Userinfo();
        queryUserinfo.setUsername("a");

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select5", queryUserinfo);
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}


public class Select6 {
    public static void main(String[] args) throws IOException {
        Map map = new HashMap();
        map.put("username", "a");

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> listUserinfo = session.selectList("select6", map);
        for (int i = 0; i < listUserinfo.size(); i++) {
            Userinfo userinfo = listUserinfo.get(i);
            System.out.println(userinfo.getId());

        }
        session.commit();
        session.close();
    }

}

标签<bind>中的代码value="'%'+username+'%'"含义是从Map中根据key名称为username找到对应的值,然后再拼接成%value%的形式。

MyBatis框架也非常支持Oracle的CLOB,不需要特别的环境配置即可完成CLOB字段的读写操作。

创建名为bigCLOB的Web项目,映射文件bigclob.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="clob">
    <select id="selectById1" resultType="map">
        select * from bigtext where
        id=1445484
    </select>
    <select id="selectById2" resultType="entity.Bigclob">
        select * from bigtext where
        id=1445484
    </select>
    <insert id="insert" parameterType="entity.Bigclob">
        <selectKey order="BEFORE" resultType="java.lang.Long"
            keyProperty="id">
            select idauto.nextval from dual
        </selectKey>
        insert into bigtext(id,bigtext) values(#{id},#{bigtext})
    </insert>
</mapper>

创建实体类代码如下。

package entity;

public class Bigclob {

    private long id;
    private String bigtext;

    public Bigclob() {
    }

    public Bigclob(long id, String bigtext) {
        super();
        this.id = id;
        this.bigtext = bigtext;
    }

    public long getId() {
        return id;
    }

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

    public String getBigtext() {
        return bigtext;
    }

    public void setBigtext(String bigtext) {
        this.bigtext = bigtext;
    }

}

默认的情况下Oracle数据表中有1条近80万行的CLOB记录,如图2-17所示。

图2-17 默认有80万行的记录

进行查询操作,将数据封装进Map的代码如下。

public class Select1 {
    public static void main(String[] args) throws IOException, SQLException {
        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        Map map = session.selectOne("selectById1");
        oracle.sql.CLOB clob = (oracle.sql.CLOB) map.get("BIGTEXT");

        Reader reader = clob.getCharacterStream();
        char[] charArray = new char[10000];
        int readLength = reader.read(charArray);
        while (readLength != -1) {
            String newString = new String(charArray, 0, readLength);
            System.out.println(newString);
            readLength = reader.read(charArray);
        }
        reader.close();
        session.commit();
        session.close();
    }

}

程序运行后在控制台打印出了最后一行信息,如图2-18所示。

图2-18 控制台输出最后一条信息

进行查询操作,将数据封装进实体类Bigclob.java的代码如下。

public class Select2 {
    public static void main(String[] args) throws IOException, SQLException {
        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        Bigclob bigclob = session.selectOne("selectById2");
        System.out.println(bigclob.getBigtext());
        session.commit();
        session.close();
        System.out.println("封装进实体类运行结束!");
    }

}

程序运行后,在控制台输出最后一行信息,如图2-19所示。

图2-19 控制台输出最后一条信息

插入新的CLOB记录的代码如下。

public class Insert {
    public static void main(String[] args) throws IOException, SQLException {
        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        Bigclob bigclob = session.selectOne("selectById2");

        session.insert("insert", bigclob);

        session.commit();
        session.close();
        System.out.println("222222");
    }

}

在数据表中插入了另外一条80万行的记录,如图2-20所示。

图2-20 两条80万行的字段

想要实现分页功能,就要先算出起始位置,起始位置的算法如下。

起始位置=(目标到达的页数-1)*一页显示的记录数

示例代码如下。

public class Select1 {
    public static void main(String[] args) throws IOException {
        String gotoPage = "a";
        int gotoPageInt = 1;
        int pageShowSize = 4;
        try {
            gotoPageInt = Integer.parseInt(gotoPage);
            if (gotoPageInt <= 0) {
                gotoPageInt = 1;
            }
        } catch (NumberFormatException e) {
            // insert db log
            gotoPageInt = 1;
        }

        int beginPosition = (gotoPageInt - 1) * pageShowSize;
        System.out.println(beginPosition);
    }

}

MyBatis还支持分页功能,创建名称为pageTest的Web项目。

映射文件配置代码如下。

<?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="userinfo">
    <select id="selectAll" resultType="entity.Userinfo">
        select * from userinfo order by id asc
    </select>
</mapper>

使用MyBatis实现分页功能的代码如下。

public class Select2 {
    public static void main(String[] args) throws IOException {

        String gotoPage = "5";
        int gotoPageInt = 1;
        int pageShowSize = 4;
        try {
            gotoPageInt = Integer.parseInt(gotoPage);
            if (gotoPageInt <= 0) {
                gotoPageInt = 1;
            }
        } catch (NumberFormatException e) {
            // insert db log
            gotoPageInt = 1;
        }

        int beginPosition = (gotoPageInt - 1) * pageShowSize;
        System.out.println(beginPosition);

        String configFile = "mybatis-config.xml";
        InputStream configStream = Resources.getResourceAsStream(configFile);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configStream);
        SqlSession session = factory.openSession();
        List<Userinfo> list = session.selectList("selectAll", null, new RowBounds(beginPosition, pageShowSize));
        for (int i = 0; i < list.size(); i++) {
            Userinfo userinfo = list.get(i);
            System.out.println(userinfo.getId() + " " + userinfo.getUsername() + " " + userinfo.getPassword() + " "
                    + userinfo.getAge() + " " + userinfo.getInsertdate());
        }
        session.commit();
        session.close();
    }

}

但MyBatis提供的分页功能在执行效率上是比较差的,它的算法是先将数据表中符合条件的全部记录放入内存,然后在内存中进行分页,这样对内存占用率较高,所以可以参考第三方的MyBatis分页插件来优化分页执行的效率。由于第三方的插件不是官方提供的,请自行查询相关资料。

创建名称为BatchTest的项目,示例代码如下。

public class InsertUserinfo {

    public static void main(String[] args) {
        try {
            Userinfo userinfo = new Userinfo();
            userinfo.setUsername("高洪岩");
            userinfo.setPassword("大中国");
            userinfo.setAge(100L);

            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserinfoMapper userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                userinfoMapper.insertUserinfo(userinfo);
            }
            long endTime = System.currentTimeMillis();

            System.out.println("非批量用时:" + (endTime - beginTime));

            //

            sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            userinfoMapper = sqlSession.getMapper(UserinfoMapper.class);

            beginTime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                userinfoMapper.insertUserinfo(userinfo);
            }
            endTime = System.currentTimeMillis();

            System.out.println("批量用时:" + (endTime - beginTime));

            sqlSession.commit();
            sqlSession.close();

            System.out.println("createId=" + userinfo.getId());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

使用批量执行SQL语句的关键代码如下。

sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

批量与非批量的时间对比如图2-21所示。

图2-21 批量与非批量执行的时间对比

如果在Oracle数据库中对序列进行池化,则两者的执行速度还会有进一步的大幅提升。如果使用固态硬盘,运行速度差距较小,如果使用机械硬盘则时间差距很大。


相关图书

Effective Java中文版(原书第3版)
Effective Java中文版(原书第3版)
Java核心技术速学版(第3版)
Java核心技术速学版(第3版)
Java编程动手学
Java编程动手学
Java研发自测入门与进阶
Java研发自测入门与进阶
Java开发坑点解析:从根因分析到最佳实践
Java开发坑点解析:从根因分析到最佳实践
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Java EE企业级应用开发实战(Spring Boot+Vue+Element)

相关文章

相关课程