1、静态SQL与动态SQL
首先,所谓SQL的动态和静态,是指SQL语句在何时被编译和执行,二者都是用在SQL嵌入式编程中的,这里所说的嵌入式是指将SQL语句嵌入在高级语言中,而不是针对于单片机的那种嵌入式编程。
在某种高级语言中,如果嵌入了SQL语句,而这个SQL语句的主体结构已经明确,例如在Java的一段代码中有一个待执行的SQL语句:“select * from t1 where c1>5”,在Java编译阶段,就可以将这段SQL交给数据库管理系统去分析,数据库软件可以对这段SQL进行语法解析,生成数据库方面的可执行代码,这样的SQL称为静态SQL,即在编译阶段就可以确定数据库要做什么事情。
首先,所谓SQL的动态和静态,是指SQL语句在何时被编译和执行,二者都是用在SQL嵌入式编程中的,这里所说的嵌入式是指将SQL语句嵌入在高级语言中,而不是针对于单片机的那种嵌入式编程。
而如果嵌入的SQL没有明确给出,如在Java中定义了一个字符串类型的变量sql语句:String sql,然后采用preparedStatement对象的execute方法去执行这个sql,该sql的值可能等于从文本框中读取的一个SQL或者从键盘输入的SQL,但具体是什么,即SQL语句的主体结构,在编译时尚无法确定,只有等到程序运行起来,在执行的过程中才能确定,这种SQL叫做动态SQL。例如每一种数据库软件都有能够执行SQL语句的界面,那个界面接收的SQL就是动态SQL,因为数据库厂商在做这个界面时,并不知道用户会输入哪些SQL,只有在该界面执行后,接收了用户的实际输入,才知道SQL是什么。
另外还要注意一点,在SQL中如果某些参数没有确定,如“select * from t1 where c1 > ? ”这种语句是静态SQL,不是动态SQL,虽然个别参数的值不知道,但整个SQL的结构已经确定,数据库是可以将它编译的,在执行阶段只需将个别参数的值补充进来即可。
静态SQL语句一般用于嵌入式SQL应用中,在程序运行前,SQL语句必须是确定的,例如SQL语句中涉及的列名和表名必须是存在的。静态SQL语句的编译是在应用程序运行前进行的,编译的结果会存储在数据库内部。而程序运行时,数据库将直接执行编译好的SQL语句,降低运行时的开销。静态SQL在编译时就已经确定了引用的表和列。
2、动态SQL在MyBatis中的应用
在讨论MyBatis中的动态SQL语句之前,首先要先打个基础,弄清楚几个基本的问题。
什么是ORM
对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据(在Java中可以用XML或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成Java对象,其本质上就是将数据从一种形式转换到另外一种形式。
MyBatis的ORM实现方式
MyBatis是一种优秀的ORM框架,目前属于Apache的一个子项目了。 相对于Hibernate “O/R”而言,MyBatis是一种“sql mapping”的ORM实现。Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO和数据库表之间的映射,以及sql的自动生成和执行。程序员往往只需定义好POJO到数据库表的映射关系,即可通过Hibernate 提供的方法完成持久层操作。程序员甚至不需要对sql的熟练掌握, Hibernate会根据制定的存储逻辑,自动生成对应的sql并调用JDBC接口加以执行。 而MyBatis的着力点,则在于POJO与sql之间的映射关系。也就是说,MyBatis并不会为程序员在运行期自动生成sql执行。具体的sql需要程序员编写,然后通过映射配置文件,将sql所需的参数,以及返回的结果字段映射到指定POJO。
使用MyBatis提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象,这一层与通过Hibernate 实现ORM而言基本一致,而对于具体的数据操作,Hibernate会自动生成sql语句,而MyBatis则要求开发者编写具体的sql语句。相对Hibernate而言,MyBatis以sql开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。 MyBatis是一个中层的框架,它比JDBC的层次高一些,但是相对于Hibernate这样的纯ORM工具,层次又要低一些。
下图展示了MyBatis框架运行的过程的主要步骤,SqlMapSession对象的创建和销毁根据不同的情况会有所不同,因为SqlMapSession负责创建数据库连接和事务管理,MyBatis既可以自己管理事务,也可以由外部管理。MyBatis自己管理事务是通过共享SqlMapSession对象来实现的,多个Statement执行时共享一个SqlMapSession实例,而且线程都是安全的。如果是外部程序管理就要自己控制SqlMapSession对象的生命周期。
MyBatis中命名空间(namespace)的作用
在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。
MyBatis中使用#和$书写占位符有什么区别
#将传入的数据都当成一个字符串,会对传入的数据自动加上引号;$将传入的数据直接显示生成在SQL中。使用#传入参数时,sql语句解析时会加上”“。
注意:$将传入的数据直接显示生成在SQL中,可能会导致SQL注射攻击。 绝大多数情况下都只用#,但是如果要做动态的排序,比如写order by column子句的时候应该用$而不是#。
MyBatis中的动态SQL实例
对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,如果不使用持久层框架我们可能需要自己拼装SQL语句,不过MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有:if 、choose、 when、otherwise、trim、where、set 、foreach。
动态SQL举例:
<select id="foo" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select>
一个更复杂的例子如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="devices">
<typeAlias alias="devicesDO"
type="com.test.model.DevicesDO" />
<typeAlias alias="devicesQueryPram"
type="com.test.model.DevicesQueryPram" />
<sql id="allColumn">
id as id,
rdc_id as rdcId,
gmt_create as gmtCreate,
gmt_modified as gmtModified,
device_id as deviceId,
device_type as deviceType,
available as available,
modified_person as modifiedPerson
</sql>
<!-- 查询参数 -->
<sql id="queryCondition">
<dynamic>
<isNotEmpty property="id" prepend="and">
id=#id#
</isNotEmpty>
<isNotEmpty property="rdcId" prepend="and">
rdc_id=#rdcId#
</isNotEmpty>
<isNotEmpty property="gmtCreate" prepend="and">
gmt_create=#gmtCreate#
</isNotEmpty>
<isNotEmpty property="gmtModified" prepend="and">
gmt_modified=#gmtModified#
</isNotEmpty>
<isNotEmpty property="deviceId" prepend="and">
device_id=#deviceId#
</isNotEmpty>
<isNotEmpty property="deviceType" prepend="and">
device_type=#deviceType#
</isNotEmpty>
<isNotEmpty property="available" prepend="and">
available=#available#
</isNotEmpty>
<isNotEmpty property="modifiedPerson" prepend="and">
modified_person=#modifiedPerson#
</isNotEmpty>
</dynamic>
</sql>
<!-- select 基本语句 -->
<select id="select" parameterClass="devicesQueryPram"
resultClass="devicesDO">
select
<include refid="allColumn" />
from devices WHERE 1 = 1
<include refid="queryCondition" />
</select>
<select id="selectByPage" parameterClass="devicesQueryPram"
resultClass="devicesDO">
select
<include refid="allColumn" />
from devices WHERE 1 = 1
<include refid="queryCondition" />
<isEqual property="needSort" compareValue="true">
order by gmt_modified desc
</isEqual>
limit #offset#, #pageSize#
</select>
<update id="update" parameterClass="devicesDO">
update devices
set gmt_modified=now()
<isNotEmpty property="deviceType" prepend=",">
device_type=#deviceType#
</isNotEmpty>
<isNotEmpty property="available" prepend=",">
available=#available#
</isNotEmpty>
<isNotEmpty property="modifiedPerson" prepend=",">
modified_person=#modifiedPerson#
</isNotEmpty>
</isNotEmpty>
where device_id = #deviceId# and rdc_id=#rdcId#
</update>
<sql id="insertColumn">
<isNotEmpty property="rdcId" prepend=",">
rdc_id
</isNotEmpty>
<isNotEmpty property="deviceId" prepend=",">
device_id
</isNotEmpty>
<isNotEmpty property="deviceType" prepend=",">
device_type
</isNotEmpty>
<isNotEmpty property="available" prepend=",">
available
</isNotEmpty>
<isNotEmpty property="modifiedPerson" prepend=",">
modified_person
</isNotEmpty>
</sql>
<sql id="insertColumnValues">
<isNotEmpty property="rdcId" prepend=",">
#rdcId#
</isNotEmpty>
<isNotEmpty property="deviceId" prepend=",">
#deviceId#
</isNotEmpty>
<isNotEmpty property="deviceType" prepend=",">
#deviceType#
</isNotEmpty>
<isNotEmpty property="available" prepend=",">
#available#
</isNotEmpty>
<isNotEmpty property="modifiedPerson" prepend=",">
#modifiedPerson#
</isNotEmpty>
</sql>
<!-- insert into 基本语句 -->
<!-- MyBatis的SQL Maps通过<insert>元素的子元素<selectKey>来支持主键自动生成 -->
<!-- 将selectKey放在insert之后,通过LAST_INSERT_ID()获得刚插入的这条记录的自增主键的值 -->
<insert id="insert" parameterClass="devicesDO">
insert into devices(id,gmt_create,gmt_modified
<include refid="insertColumn" />
)
values(#id#,now(),now()
<include refid="insertColumnValues" />
)
<selectKey keyProperty="id" resultClass="java.lang.Long" >
SELECT LAST_INSERT_ID() AS value
</selectKey>
</insert>
</sqlMap>
一个简单的MyBatis配置文件与相应代码的对应关系如下图所示:
引申探讨
为什么现在企业里针对多对多关系的数据表很少用外键?难道是级联操作不安全?
具体场景:数据表设计的时候,师兄说尽量不要用外键,在学校里学习数据库的时候老师就是这么教的,两个多对多关系的表,需要再建第三个表来表明关系。为什么在企业里就不能用呢?
如果是在互联网企业, 除非是支付相关的业务, 不推荐使用诸如外键, 事务等数据库约束, 也不推荐使用联表查询。你师兄的意思不是不让你用多对多的关系表, 而是不要在这张表上加外键约束。原因有几个方面:
(1)Mysql在innodb引擎出现之前, 常见的引擎如MyIsam是不支持这些数据库约束的。
(2)这些数据库约束包括联表查询, 效率是无法满足互联网应用的要求的, 你想象一下一个新浪微博的大V如果被删除了, 它的粉丝关系表里几百万的数据如果有外键约束, 会出现什么情况(当然微博肯定不是这么直接删除用户数据的)。
(3)无法扩展, 如果这其中某些表要进行分库分表,这些约束都是没法扩展的,而可扩展性对一个互联网应用的意义不用我多说了吧。