5.执行SQL语句
前面我们对mybatis已经配置得差不多了,现在差不多就可以使用mybatis来执行SQL语句了。在项目的Test测试文件夹中创建一个MybatisTest的一个Java测试类,我们同时要到导入junit的maven依赖:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
然后创建一个名为testSelectAll的测试方法,编写测试select语句的相关代码,代码如下:
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.haiexijun.entity.Goods; import org.junit.Test; import java.io.IOException; import java.io.Reader; import java.util.List; public class MybatisTest { @Test public void testtestSelectAll() throws IOException { //利用Reader加载classpath下的mybatis-config.xml环形配置文件 Reader reader=Resources.getResourceAsReader("mybatis-config.xml"); //初始化SqlSessionFactory SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession=null; try { sqlSession=sqlSessionFactory.openSession(); sqlSession.selectList("goods.selectAll"); List<Good> goods= sqlSession.selectList("goods.selectAll"); for (Good g:goods){ System.out.println(g.getSubTitle()); } }catch (Exception e){ e.printStackTrace(); }finally { //如果type=”POOLED",代表连接池,close则是将连接回收到连接池中 //如果type=“UNPOOLED",代表将连接关闭 if (sqlSession!=null){ sqlSession.close(); } } } }
运后能成功查询到相关的数据。
下面对代码进行一些步骤的解释:
(1)构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory是Mybatis的核心对象。用于初始化Mybatis,创建SqlSession对象。要保证SqlSessionFactory在全局中唯一。
SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 可以使用任意的输入流(Reader、InputStream)实例。
如:
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
(2)从 SqlSessionFactory 中获取 SqlSession
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
SqlSession是Mybatis操作数据库的核心对象,底层使用JDBC与数据库进行交互。 SqlSession对象提供了数据表CRUD对应的方式。
看到这里,就算完成了对Mybatis的最基本的操作了。恭喜你!入门了。但是这只是入门而已,我们仍然能发现代码的很多问题。比如,我们的代码无法保证SqlSessionFactory在全局中唯一。所以我们可以对代码做进一步的优化改善。所以下面要封装一个MybatisUtils初始化工具类来对我们的代码进一步优化。
6.封装初始化工具类MybatisUtils
在项目的main下Java下创建一个org.haiexijun.utils的工具包,用于存放我们封装的工具类。
然后再utils包下面创建一个叫做MybatisUtils的java类。代码如下:
package org.haiexijun.utils; 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 java.io.IOException; import java.io.Reader; //工具类的方法和属性一般都用static关键字修饰 public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory=null; //用于初始化静态对象 static { Reader reader= null; try { reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { e.printStackTrace(); //把异常抛出去,让使用到这个工具类的程序也知道我们这段代码出错了 //ExceptionInInitializerError是初始化异常 throw new ExceptionInInitializerError(e); } } //创建一个方法获取SqlSession对象 public static SqlSession openSession(){ return sqlSessionFactory.openSession(); } //关闭(回收)SqlSession的连接的方法 public static void closeSession(SqlSession session){ if (session !=null){ session.close(); } } }
我们编写好MybatisUtils初始化工具类后,我们再到之前编写的MybatisTest测试类中对它进行测试,代码如下:
@Test public void testMybatisUtils(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); List<Good> goods= sqlSession.selectList("goods.selectAll"); for (Good g:goods){ System.out.println(g.getSubTitle()); } }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
结果显示,使用MybatisUtils工具类后,既保证了SqlSessionFactory在全局中唯一,也使我们的代码更加的直观和整洁了。
你以为到这里就结束了吗?并没有哦。我们的代码还有一个很严重的bug没有解决。我们先来对比一下数据库中的t_goods数据表的字段名和我们实体类定义的字段名。如下图:
Good的entity实体类的字段名:
我们会发现,有些字段的名字是相互对应的关系,如title和discount这两个字段。但如果字段的名字是由两个以上单词组成时,就不是相互对应的关系了。如good_id这个字段我们在实体里面写为goodId这种大驼峰的写法,但这种定义Mybatis无法对其识别。如果我们这时查询goodId,则查询结果全为null。解决办法就是对mybatis的核心配置文件中增加一个设置项<settings>,之后mybatis就会自动对数据表进行驼峰命名转换了。
在mybatis-config.xml中新增加如下代码:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
7.设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。我们就是在里面设置对数据表的驼峰命名转化的。
下面我们列出一些setting的选项:
一个配置完整的 settings 元素的示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
8.SQL传参
前面案例里面,有用到select,查询t_goods表的所有的数据。但是,很多时候,我们要查询的数据可能并不是全部数据,而是我们指定要查询的数据。这时候,我们就不能再像以前那样把select语句给写死了。而是根据用户的输入,动态传入参数来查询。
传入一个参数时的写法
要实现SQL传参,就要用到select元素的一个属性:parameterType,来指定参数的类型。
比如我要通过Id值来查询数据:
<select id="selectById" parameterType="Integer" resultType="org.haiexijun.entity.Goods"> select * from t_goods where goods_id = #{value} </select>
parameterType指定传入的参数为Integer类型,然后在select语句里面用#{ }来表示传入的参数的占位。有点像JDBC里面preparedstatement的 ?。
在MybatisTest测试类中编写测试方法:
@Test public void testSelectById(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); Good good= sqlSession.selectOne("goods.selectById",1603); System.out.println(good.getTitle()); }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
注意: 因为是通过id来查询数据,返回的数据只有一条。所以要用selectOne()方法 ,这个方法传入两个参数,第二个参数是sql语句要传入的参数值,要与之前select元素里面编写的parameterType要一致。比如传入1603,就指查找id为1603的good的信息。
要传入多个参数时的写法
如果要查询某一价格范围内的商品的信息,传入的参数为最大值和最小值这两个值,此时一个参数就不能完成查询了。但是Mybatis只支持设置一个parameterType。
遇到多参数的情况下,我们可以通过设置parameterType的值为java.util.Map,键值对的形式来传入多个参数。
新建一个select查询:
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="org.haiexijun.entity.Goods"> select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit} </select>
可以看到,我们把parameterType的值定义为java.util.Map,select语句要传入三个参数:min,max和limit。
在MybatisTest测试类中编写测试方法
@Test public void testSelectByPriceRange(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); Map param=new HashMap(); param.put("min",100); param.put("max",500); param.put("limit",10); List<Good> goods= sqlSession.selectList("goods.selectByPriceRange",param); for (Good g:goods){ System.out.println(g.getTitle()+":"+g.getCurrentPrice()); } }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
我们创建了HashMap,并传入3个键值对,这三个键值对与select语句中要传入的参数一 一对应。键名也要对应。
9.多表关联查询
在实际开发中,大量的企业级应用,都是多表联合查询所产生的一个复杂的结果集。查询的结果字段会横跨很多张表。
获取多表关联查询结果
如果要进行多表关联查询,那么查询结果的类型就不是某个具体的实体(entity)了。此时我们要把resultType设置为java.util.Map。也就是说,多表关联查询后返回的结果为map类型。
编写一条select语句,查询t_goods表和t_category这两个表的中category_id相同的结果。
<select id="selectGoodsMap" resultType="java.util.Map"> select g.* , c.category_name from t_goods g,t_category c where g.category_id = c.category_id </select>
在MybatisTest测试类中编写测试方法:
@Test public void testSelectGoodsMap(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); List<Map> list= sqlSession.selectList("goods.selectGoodsMap"); for (Map m:list){ System.out.println(m); } }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
运行这个测试方法后,如下:
我们看到所有的键名都是原数据表的字段名。而且会发现虽然Map可以存储查询结果,但是存储的结果并不是按顺序来排列的。这是因为HashMap是按哈希值来排序的。
把resultType设置为java.util.LinkedHashMap,因为LinkedHashMap是链表形式的HashMap,会按照顺序来存取。
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap"> select g.* , c.category_name from t_goods g,t_category c where g.category_id = c.category_id </select>
更改后运行代码,就有序了:
利用LinkedHashMap,保存多表关联查询结果,Mybatis会将每条记录都包装为LinkedHashMap对象。Key是字段名,value是字段对应的属性值。字段类型会根据表结构自动判断。
优点:易于拓展和使用
缺点:太过于灵活了,无法进行编译时检查。
10.ResultMap结果映射
ResultMap可以将查询结果映射为复杂类型的java对象
ResultMap适用于Java对象保存多表关联的结果
ResultMap支持对象关联查询等高级特性。
resultMap 元素是 MyBatis 中最重要最强大的元素。
写一个案例来体验一下结果映射:
上面的案例我们用LinkedHashMap来保存了多表联合查询的结果。
如果此次我想要把多表联合查询的结果保存为Java对象的话,就要用到ResultMap结果映射。
先创建一个新的包:org.haiexijun.dto。
DTO(data transfer object)数据传输对象,是一个特殊的javaBean,对原始的对象进行扩展,用于数据保存和传递。
在org.haiexijun.dto包下面创建一个叫GoodsDTO的Java类。下面是代码:
package org.haiexijun.dto; import org.haiexijun.entity.Goods; public class GoodsDTO { //Goods实体的引用 private Goods goods=new Goods(); //其他字段 private String categoryName; public Good getGoods() { return goods; } public void setGoods(Good goods) { this.goods = goods; } public String getCategoryName() { return categoryName; } public void setCategoryName(String categoryName) { this.categoryName = categoryName; } }
那么针对于这个DTO对象,如何让Mybatis自动对其进行对应的赋值呢?在这,我们就要使用ResultMap了。
配置ResultMap
(1)在goods.xml映射文件中创建新建一个resultMap节点,并对其进行配置:
<!--结果映射--> <resultMap id="rmGoods" type="org.haiexijun.dto.GoodsDTO"> <!--设置主键字段和属性映射--> <id property="goods.goodsID" column="good_id"/> <!--设置非主键字段与属性映射--> <result property="goods.title" column="title"/> <result property="goods.subTitle" column="sub_title"/> <result property="goods.originalCost" column="original_cost"/> <result property="goods.currentPrice" column="current_price"/> <result property="goods.discount" column="discount"/> <result property="goods.isFreeDelivery" column="is_free_delivery"/> <result property="goods.categoryId" column="category_id"/> <!--特殊的DTO的字段,property直接传入字段名--> <result property="categoryName" column="category_name"/> </resultMap>
resultMap的id属性是为了被下面的select元素引用而定义的。type表示把结果存为哪个DTO。resultMap还要顶义一些子标签,id标签是设置主键字段和属性映射,result标签是设置非主键字段与属性映射。每个子标签都有两个属性:property和column。column表示查询的字段名,property这指向了GoodsDTO里面的每个属性的名称,如果一个属性是额外的属性,则直接传入属性名。
(2)在goods.xml映射文件中新建一个select元素,并为其指定resultMap属性,属性值为上面定义的resultMap的id属性值rmGoods:
<select id="selectGoodsDTO" resultMap="rmGoods"> select g.* , c.category_name from t_goods g,t_category c where g.category_id = c.category_id </select>
- 在MybatisTest测试类中编写测试方法:
@Test public void testSelectGoodsDTO(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); List<GoodsDTO> list= sqlSession.selectList("goods.selectGoodsDTO"); for (GoodsDTO g:list){ System.out.println(g.getGoods().getTitle()); } }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }