Mybatis总结(一)

Mybatis总结(一)

一.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="db.properties" ></properties>
    <typeAliases>
        <package name="com.courage.mybatis.entity"/>
    </typeAliases>
    <typeHandlers>
        <typeHandler handler="com.courage.mybatis.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="Integer"></typeHandler>
    </typeHandlers>
    <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="com/courage/com.courage.mybatis/mapper/StudentMapper.xml"/>
    </mappers>

</configuration>

文件头

这一部分是Mybatis用来确认的配置文件,相当于java中的魔数,主要用来检验

<?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">

数据源多处配置,properties加载顺序

<properties resource="db.properties" ></properties>

这一段主要是指定数据库的配置文件,properties中是可以设置属性的,如下:

<!-- 加载数据库属性文件 -->
<properties resource="db.properties">
    <property name="username" value="Courage"/>
</properties>

那么有一个问题,就是如果数据库信息在config.xml、db.properties、以及properties中都有配置,那么会读取哪一个呢?或者说读取顺序是什么呢?直接上源码:

private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    /**
     *  这一部分是解析properties属性中指定的属性,即上面的Courage部分。
     */
    Properties defaults = context.getChildrenAsProperties();
    //resource 制定的属性路径
    String resource = context.getStringAttribute("resource"); 
      
    String url = context.getStringAttribute("url"); //url制定的属性路径
    if (resource != null && url != null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
    /**
     * 根据 properties 元素中的 resource 属性读取类路径下属性文件(db.properties),并覆盖properties 属性中指定的同名属性(Courage)。
     */
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      /**
       * 根据properties元素中的url(db.properties中的)属性指定的路径读取属性文件,并覆盖properties 属性中指定的同名属性(Courage那一部分)。
       */
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    /**
     *  获取方法参数传递的properties
     *  创建XMLConfigBuilder实例时,this.configuration.setVariables(props);
     */
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
      
    parser.setVariables(defaults);
    configuration.setVariables(defaults);
  }
}

结论

1、在 properties 内部自定义的属性值第一个被读取(这部分:)

2、然后读取 resource 路径表示文件中的属性(db.properties),如果有,它会覆盖已经读取的属性(Courage);如果 resource 路径不存在,那么读取 url (config.xml)表示路径文件中的属性,如果有,它会覆盖第一步读取的属性值(Courage)

3、最后读取 parameterType (程序员编码时设置的值)传递的属性值,它会覆盖已读取的同名的属性

二.别名设置

typeAliases

typeAliases主要是用来给对应的类取一个别名,查询语句中,返回类型输入参数类型都是以包名+类名,如:com.courage.mybatis.entity.Student的形式,来来回回复制粘贴很麻烦,那么就可以采用取别名的方法:

单个别名:
<typeAlias type="com.courage.mybatis.entity.Student" alias="student"></typeAlias>

以为着之前包名+类名的地方都可以用student代替,student是忽略大小写的.

批量别名:
<package name="com.courage.mybatis.entity"/>

如果包里很多类,那么注意指定别名工作量也很大,那么可以采用这种批量别名的方式对一个包里面的类进行别名,这是类的别名就是不带包名的类名,同样忽略大小写,除了自定义别名外,Mybatis还内置了一些常见的类的别名:

Mapped Type
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

三.类型处理器

typeHandlers

数据库中的表映射成类时,某些字段可能没法匹配,例如int类型与boolean类型转换,所以需要人工写一些转换对应的规则

<typeHandlers>
    <typeHandler handler="com.courage.mybatis.converter.BooleanAndIntConverter" 						javaType="Boolean" jdbcType="Integer"></typeHandler>
</typeHandlers>

其中BooleanAndIntConverter就是一个Boolean与int相互转换的一个类型转换类:

转换器代码

public class BooleanAndIntConverter extends BaseTypeHandler<Boolean> {

    //java(boolean)-DB(number)
    /*
     * ps:PreparedStatement对象
     * i:PreparedStatement对象操作参数的位置
     * parameter:java值
     * jdbcType:jdbc操作的数据库类型
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
            throws SQLException {
        if (parameter) {
            //1
            ps.setInt(i, 1);
        } else {
//				0
            ps.setInt(i, 0);
        }
    }
    //db(number)->java(boolean)
    @Override
    public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int sexNum = rs.getInt(columnName);//rs.getInt("stuno") ;
        return sexNum == 1 ? true : false;
    }
    @Override
    public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int sexNum = rs.getInt(columnIndex);//rs.getInt(1)
        return sexNum == 1 ? true : false;
    }
    @Override
    public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int sexNum = cs.getInt(columnIndex);//rs.getInt(1)
        return sexNum == 1 ? true : false;
    }
}

转换器的使用

1.在config.xml中配置
    <typeHandlers>
        <typeHandler handler="com.courage.mybatis.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="Integer"></typeHandler>
    </typeHandlers>
2.在Mapper.xml中使用
<select id="queryStudentByStunoWithConverter" 	
                        parameterType="int"
                        resultMap="studentResult" >
    select * from student where stuno = #{stuno}
</select>
<!--通过studentResult将二者联系起来-->
<resultMap type="student" id="studentResult">
    <id property="stuNo"  column="stuno"  />
    <result property="stuName"  column="stuname" />
    <result property="stuAge"  column="stuage" />
    <result property="graName"  column="graname" />
    <result property="stuSex"  column="stusex" javaType="boolean"jdbcType="INTEGER"/>
</resultMap>
注意:

​ 1.将boolean 与 INTEGER对应起来,并且指定类型,注意这儿只能大写,小写不行

​ 2.查询类型转换器

1.如果 类中属性 和 表中的字段 类型能够合理识别(String-varchar2),

则可以使用resultType;否则(boolean-number)使用resultMap

2.如果 类中属性名 和表中的字段名 能够合理识别 (stuNo -stuno)

则可以使用resultType;否则(id-stuno) 使用resultMap

四.全局设置

1.参数配置方法以及位置

可以设置一些全局参数配置,配置位置很重要,在properties下面,在environments上面,通过key- value队进行设置,不熟悉的话不建议随便设置这个值,牵一发而动全身,影响mybatis的行为

<settings>
        <setting name="cacheEnabled" value="false"/>
        <setting name="lazyLoadingEnable" value="false"/>
</settings>

2.参数表

可以设置的参数以及描述:

设置名 描述 有效值 默认值
cacheEnabled 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。 true | false false (在 3.4.1 及之前的版本默认值为 true)
multipleResultSetsEnabled 是否允许单一语句返回多结果集(需要驱动支持)。 true | false true
useColumnLabel 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true | false true
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。 true | false False
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日志等级必须设置为 WARN)FAILING: 映射失败 (抛出 SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 任意正整数 未设置 (null)
defaultFetchSize 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。 任意正整数 未设置 (null)
safeRowBoundsEnabled 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 true | false False
safeResultHandlerEnabled 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。 true | false True
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false False
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 SESSION | STATEMENT SESSION
jdbcTypeForNull 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType 常量,常用值:NULL, VARCHAR 或 OTHER。 OTHER
lazyLoadTriggerMethods 指定哪个对象的方法触发一次延迟加载。 用逗号分隔的方法列表。 equals,clone,hashCode,toString
defaultScriptingLanguage 指定动态 SQL 生成的默认语言。 一个类型别名或完全限定类名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) 一个类型别名或完全限定类名。 org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 true | false false
returnInstanceForEmptyRow 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。(新增于 3.4.2) true | false false
logPrefix 指定 MyBatis 增加到日志名称的前缀。 任何字符串 未设置
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 以上)
vfsImpl 指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。 未设置
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) true | false true
configurationFactory 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) 类型别名或者全类名. 未设置

五.存储过程

1.存储过程的简介

我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。

一个存储过程是一个可编程的函数,它在数据库中创建并保存。它可以有SQL语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。

2.存储过程优点

  1. 存储过程增强了SQL语言的功能和灵活性。存储过程可以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。
  2. 存储过程允许标准组件是编程。存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响。
  3. 存储过程能实现较快的执行速度。如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
  4. 存储过程能过减少网络流量。针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的Transaction-SQL语句被组织程存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大增加了网络流量并降低了网络负载。
  5. 存储过程可被作为一种安全机制来充分利用。系统管理员通过执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。

3.存储过程缺点

  1. 不易维护,一旦逻辑变了修改起来麻烦
  2. 如果写此存储过程的人离职了,对于接手她代码的人估计是一场灾难,因为别人还要去读懂你程序逻辑,还要读懂你存储逻辑。不利于扩展。
  3. 最大的缺点! 虽然存储过程可以减少代码量,提高开发效率。但是有一点非常致命的就是太耗性能。

4.存储过程的语法

a.创建存储过程

create procedure sp_name()
begin
.........
end

b.调用存储过程

call sp_name()

c.删除存储过程

drop procedure sp_name//

d.其他常用命令

显示数据库中所有存储的存储过程基本信息,包括所属数据库,存储过程名称,创建时间等

show procedure status

显示某一个MySQL存储过程的详细信息

show create procedure sp_name

5.Mybatis调用MySQL存储过程

a.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>  

    <settings>
    <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    <!-- 配置别名 -->  
    <typeAliases>  
        <typeAlias type="com.lidong.axis2demo.DevicePOJO" alias="DevicePOJO" />   
    </typeAliases>  
    <!-- 配置环境变量 -->  
    <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://127.0.0.1:3306/mybatis?characterEncoding=GBK" />  
                <property name="username" value="root" />  
                <property name="password" value="123456" />  
            </dataSource>  
        </environment>  
    </environments>  
    <!-- 配置mappers -->  
    <mappers>  
        <mapper resource="com/lidong/axis2demo/DeviceMapper.xml" />  
    </mappers>  
</configuration>

b.实体类

public class DevicePOJO{
     private String devoceName;//设备名称
     private String deviceCount;//设备总数
    public String getDevoceName() {return devoceName;}
    public void setDevoceName(String devoceName) {
        this.devoceName = devoceName;
    }
    public String getDeviceCount() {return deviceCount;}
    public void setDeviceCount(String deviceCount) {
        this.deviceCount = deviceCount;
    }
}

c.interface

public interface DeviceDAO {
    /**
     * 调用存储过程 获取设备的总数
     * @param devicePOJO
     */
    public void count(DevicePOJO devicePOJO);

}

d.Mapper的实现

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lidong.axis2demo.DeviceDAO">
    <resultMap id="BaseResultMap" type="CusDevicePOJO">
        <result column="device_sn" property="device_sn" jdbcType="VARCHAR" />
    </resultMap>
    <sql id="Base_Column_List">
        device_sn, device_name,device_mac
    </sql>
    <select id="count" parameterType="DevicePOJO" useCache="false"
        statementType="CALLABLE">  
        <![CDATA[ 
        call countDevicesName(
        #{devoceName,mode=IN,jdbcType=VARCHAR},
        #{deviceCount,mode=OUT,jdbcType=INTEGER});
        ]]>
    </select>
</mapper>  
注意

1.statementType=”CALLABLE” 必须为CALLABLE,告诉MyBatis去执行存储过程, 否则会报错
Exception in thread “main” org.apache.ibatis.exceptions.PersistenceException

2.mode=IN 输入参数 mode=OUT输出参数 jdbcType为数据库定义的字段类型。
这样写 Mybatis会帮助我们自动回填输出的deviceCount的值。

e.测试

public class TestProduce {
    private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;  
    private static SqlSessionFactory sqlSessionFactory;  
    private static void init() throws IOException {  
        String resource = "mybatis-config.xml";  
        Reader reader = Resources.getResourceAsReader(resource);  
        sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
        sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);  
    }  
    public static void main(String[] args) throws Exception {
        testCallProduce();
    }
    /**
     * @throws IOException
     */
    private static void testCallProduce() throws IOException {
        init();
        SqlSession session= sqlSessionFactory.openSession();  
        DeviceDAO deviceDAO = session.getMapper(DeviceDAO.class);  
        DevicePOJO device = new DevicePOJO();  
        device.setDevoceName("设备名称");  
        deviceDAO.count(device);
        System.out.println("获取"+device.getDevoceName()+"设备的总数="+device.getDeviceCount());
    }
}

六.$和#两种符号

在Mybatis中,#和$有些区别,总结如下:

1.二者解析不同

使用${}方式传入的参数,mybatis不会对它进行特殊处理,而使用#{}传进来的参数,mybatis默认会将其当成字符串。可能在赋值给如id=#{id}和id=${id}看不出多大区别,但是作为表名或字段参数时可以明显看出,可以看看下面的例子:

假设传入的参数为表名test

selec * from #{table};

解析后是:

select * from "test"; 

select * from ${table};

解析后是:

select * from test;

很明显,前者多了字符串的引号,会失败,后者正常查询会成功;

所以对于传入分组(order)字段或者排序字段(order),应使用${},避免出现order by "id" 等情况。

#和$在预编译处理中是不一样的。#类似jdbc中的PreparedStatement,对于传入的参数,在预处理阶段会使用?代替,比如:

select * from student where id = ?;

待真正查询的时候即在数据库管理系统中(DBMS)才会代入参数。

而${}则是简单的替换,如下:

select * from student where id = 2;

2.适应的场景不同

#{}自动给String类型加单引号 (自动类型转换)

${} 原样输出,但是适合于 动态排序(动态字段)

select stuno,stuname,stuage from student where stuname = #{value}

select stuno,stuname,stuage from student where stuname = '${value}'

动态排序:
select stuno,stuname,stuage from student order by ${value} asc

3.防止SQL注入

所谓sql注入,就是指把用户输入的数据拼接到sql语句后面作为sql语句的一部分执行,例如:

select * from user where name=' "+name+" ' and password=' "+password+" '

那么只要用户输入用户名admin和密码123456' or 'abc' = 'abc',那么拼接出来的语句就为

select * from user where name=' admin ' and password='123456' or 'abc'= 'abc';

这样只要user表有数据,就会返回结果,达到sql注入的目的。同样,用户输入用户名a'则 and password=' "+password+" '就会被注释掉,也达到注入sql的目的。

附:

这里顺带提下防止sql注入的几种方式:

(1)、jdbc使用 PreparedStatement代替Statement, PreparedStatement 不仅提高了代码的可读性和可维护性.而且也提高了安全性,有效防止sql注入;

(2)、在程序代码中使用正则表达式过滤参数。使用正则表达式过滤可能造成注入的符号,如' --等

(3)、在页面输入参数时也进行字符串检测和提交时进行参数检查,同样可以使用正则表达式,不允许特殊符号出现。

posted @ 2020-12-07 00:22  等不到的口琴  阅读(216)  评论(0编辑  收藏  举报