- Java EE核心框架实战
- 高洪岩
- 213字
- 2020-06-27 09:36:35
第2章 MyBatis 3常用技能
本章是MyBatis框架扩展的章节,本章的内容在使用MyBatis框架时一定会用到,尤其是动态SQL在使用MyBatis框架时较为常用,在本章中读者应该着重掌握如下内容:
· 使用Properties对象连接数据库。
· 动态SQL中要使用的标签。
· 动态SQL中对null值的处理。
· 对超大字符串及分页的处理。
2.1 MyBatis 3的SQL映射文件
因为MyBatis框架是基于SQL映射的,所以SQL映射文件在此框架中的位置非常重要,但好在SQL映射文件非常简单。本章将介绍SQL映射文件的具体内容及实例使用。
2.2 连接DB数据库的参数来自于Properties对象
在前面的示例中,连接数据库时的具体参数是直接在mybatis-config.xml文件中进行定义的,比如url.username和password这些信息。MyBatis还支持将这些参数值写入*.properties属性文件中。
更改mybatis-config.xml配置文件的部分代码如下。
<dataSource type="POOLED"> <property name="driver" value="${drivername}" /> <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 drivername=com.microsoft.sqlserver.jdbc.SQLServerDriver username=sa password=
使用如下Java代码即可进行数据库的操作:
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 sqlSsession = sqlSessionFactory.openSession();
2.3 <resultMap>标签
如果数据表中字段的名称和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>
实体类Userinfo.java的结构如图2-1所示。
图2-1 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-2所示。
图2-2 成功输出数据表中的数据
2.4 <sql>标签
重复的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和insertdate这4个字段在映射文件中多处出现,所以可以将这4个字段封装进<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-3所示。
图2-3 程序运行结果
2.5 将SQL语句作为字符串变量传入
在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(); } } }
上述代码从map中get(字段名称)的形式取得字段对应的值,但字段名称在Oracle中是大写字母,所以这里的字段名称也必须要写成大写形式。为了支持方便的小写形式,可以在映射文件中为定义的SQL语句为字段指定一个别名。
select id "id",username "username",password "password",age "age"
这样从map中就可以以小写的形式取得字段值。
运行结果如图2-4所示。
图2-4 运行结果
2.6 动态SQL的使用
MyBatis框架还支持动态SQL标签的使用,而且某些标签的使用率还非常高。
2.6.1 插入null值时的处理第1种方法—jdbcType
创建名为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-5所示。
图2-5 成功插入数据表
2.6.2 插入null值时的处理第2种方法—<if>
通过在#{}格式中加入jdbcType即可避免插入null值时的异常,其实使用动态SQL标签也可以达到同样的效果。
在映射文件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-6所示。
图2-6 成功插入两条记录
2.6.3 <choose>标签的使用
<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>
创建名为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-7所示。
图2-7 userinfo数据表中的内容
程序运行后,在控制台输出如图2-8所示的信息。
图2-8 运行结果
2.6.4 <set>标签的使用
<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-9所示。
图2-9 成功更新
2.6.5 <foreach>标签的使用
<foreach>标签有循环的功能,可以用来生成有规律的SQL语句。
<foreach>标签主要的属性有item、index、collection、open、separator和close。
item表示集合中每一个元素进行迭代时的别名,index指定一个名字,用于表示在迭代过程中,每次迭代到的位置,open表示该语句以什么开始,separator表示在每次迭代之间以什么符号作为分隔符,close表示该语句以什么结束。
在映射文件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>
创建名为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-10所示的结果。
图2-10 控制台输出的结果
2.7 插入超大的字符串文本内容
MyBatis框架也支持Oracle的CLOB,不需要特别的环境配置即可完成对CLOB字段的操作。
创建名为bigCLOB的Web项目,映射文件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="insertBigString" parameterType="map"> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long"> select idauto.nextval from dual </selectKey> insert into bigclob(id,bigstring) values(#{id},#{bigstring}) </insert> <select id="selectById" resultType="map"> select * from bigclob where id=10000 </select> </mapper>
创建名为test的Servlet,核心代码如下。
public class test extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { SqlSession sqlSessionRef = GetSqlSession.getSqlSession(); Map map = sqlSessionRef.selectOne("selectById"); Clob clobRef = (Clob) map.get("BIGSTRING"); Reader reader = clobRef.getCharacterStream(); BufferedReader brRef = new BufferedReader(reader); char[] charArray = new char[2048]; int readLength = brRef.read(charArray); while (readLength != -1) { String bigString = new String(charArray, 0, readLength); System.out.println(bigString); readLength = brRef.read(charArray); } Map insertMap = new HashMap(); insertMap.put("id", 20000); insertMap.put("bigstring", clobRef); sqlSessionRef.insert("insertBigString", insertMap); sqlSessionRef.commit(); sqlSessionRef.close(); } catch (SQLException e) { // TODO Auto-generated catch block
e.printStackTrace(); } } }
默认情况下Oracle数据表中有一条包含50万行的CLOB字段,如图2-11所示。
图2-11 默认有50万行的字段
程序运行后,在控制台输出了最后一行信息,如图2-12所示。
图2-12 控制台输出的最后一条信息
数据表中还插入了另外一条50万行的记录,如图2-13所示。
图2-13 两条50万行的字段
2.8 分页
MyBatis还支持分页功能,创建名为pageTest的Web项目。
数据表userinfo中的记录内容如图2-14所示。
图2-14 userinfo数据表中的内容
映射文件的配置代码如下。
<?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="selectPage" resultType="map"> select * from userinfo </select> </mapper>
创建名为test的Servlet,代码如下:
public class test extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { SqlSession sqlSession = GetSqlSession.getSqlSession(); List<Map> listMap = sqlSession.selectList("selectPage", null, new RowBounds(2, 4)); // 2代表从第2个记录开始,往后移动4个记录 for (int i = 0; i < listMap.size(); i++) { Map eachMap = listMap.get(i); System.out.println("page中的内容: " + eachMap.get("ID") + " " + eachMap.get("USERNAME") + " " + eachMap.get("PASSWORD") + " " + eachMap.get("AGE") + " " + eachMap.get("INSERTDATE")); } } }
程序运行后,在控制台输出如图2-15所示的结果。
图2-15 输出分页数据