看过之前的蜕变系列文章,相信你对mybatis有了初步的认识。但是这些还不够,我们今天进一步来了解下mybatis的一些用法。
我们第一个程序存在很多问题,每一次操作,都需要读取配置文件、初始化mybati框架。这样搞出来的程序上就一个字——渣!这让我想起了多年以前,某个小伙伴告诉我spring的正确使用一样,每次方法都让spring框架重新初始化了一次。哈哈,知道你也在看的,又是一波回忆杀。
在第一个mybatis程序中,获取SqlSession对象的方式也比较复杂,获取SqlSession对象的操作比较复杂,由于SqlSessionFactory本身就是用来管理SqlSession对象的,应用中一般情况下有一个就足够了!我们做一个小优化——使用单利模式来封装下,让一个SqlSessionFactory对象去管理SqlSession对象。
1.创建一个MyBatisUtil工具类:
package com.pz.route.util; 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 class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static{ //读取主配置文件 InputStream input = null; if (sqlSessionFactory == null) { try { input = Resources.getResourceAsStream("mybatis.xml"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (MyBatisUtil.class) { if (sqlSessionFactory == null){ sqlSessionFactory = new SqlSessionFactoryBuilder().build(input); } } } } publicstatic SqlSession getSqlSession() { returnsqlSessionFactory.openSession(); } }
上面的例子使用了静态代码块的方式创建了一个SqlSessionFactory对象,将SqlSession交给SqlSessionFactory进行管理。 2. 修改TravelRouteDaoImpl类:
package com.pz.route.dao.impl; import org.apache.ibatis.session.SqlSession; import com.pz.route.dao.TravelRouteDao;import com.pz.route.domain.TravelRoute;import com.pz.route.util.MyBatisUtil; public class TravelRouteDaoImpl implements TravelRouteDao { private SqlSession sqlSession; @Override public void add(TravelRoute travelRoute) { try { sqlSession = MyBatisUtil.getSqlSession(); //新增数据操作 sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute); //提交SqlSession sqlSession.commit(); //无需再做关闭,SqlSessionFactory会自动关闭sqlSession } catch (Exception e) { e.printStackTrace(); } } }
SqlSession继承了AutoCloseable接口,SqlSessionFactory会自动关闭SqlSession。
数据库的一些信息最好不要写死在xml里,大家基本上都是通过配置的方式读取数据库相关信息。在resources目录下创建db.properties配置文件,里面填写下面内容:
jdbc.driverClassName=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/route?characterEncoding=utf8jdbc.username=rootjdbc.password=123456
修改mybatis.xml文件,使用将数据库连接信息修改为使用配置文件的方式:
<?xml version="1.0"encoding="UTF-8" ?>/span> PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <properties resource="db.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver"value="${jdbc.driverClassName}"/> <property name="url"value="${jdbc.url}"/> <property name="username"value="${jdbc.username}"/> <property name="password"value="${jdbc.password}"/> dataSource> environment> environments> <mappers> <mapper resource="mapper/TravelRouteMapper.xml"/> mappers>configuration>
我们新建了Mybatis.xml作为使用Mytatis的主配置文件,那么这个主配置文件应该干些什么事情呢?
1.配置外部配置文件,方便程序使用(比如配置db.properties)
2.设置全局Domain类JavaBean的别名(每次使用类的名称空间写起来太麻烦了)
3.配置运行环境
4.配置mapper文件(有哪些数据操作使用了mybatis需要要告诉框架)
5. 设置全局的数据库操作配置,比如事务超时时间等等信息
我们看看mybatis.xml中的内容:
<properties resource="db.properties"/
这样配置以后,mybatis就可以从db.properties中获取数据库信息了。
我们看看这个配置我们在mapper文件中的写法:
<insert id="add"parameterType="com.pz.route.domain.TravelRoute">
在mapper中的parameterType需要使用类的名称空间:包名+类名的方式。每次这样写比较麻烦,也比较容易写错。MyBatis提供了标签用以定义别名。
<typeAliases> <typeAlias type="com.pz.route.domain.TravelRoute"alias="TravelRoute"/> typeAliases>
这样子,在mapper中就可以直接使用TravelRoute来代替之前的 com.pz.route.domain.TravelRoute。
一个项目上线,需要经历多个阶段,一般来讲一个项目需要经过开发,测试,上线的生命周期。那么不可避免的,一个项目会有多个运行环境。
比如在开发阶段,应用往往运行在本地,测试阶段,会运行在测试服务器,到了应用上线之后,应用自然运行在生产机器上。在不同的环境下,数据库也会有多个,比如开发环境在A库,测试时在B库,项目上线以后在生产库C,那么为了支持不同的环境,mybatis提供了<environments>标签来支持多个环境的问题。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver"value="${jdbc.driverClassName}" /> <property name="url"value="${jdbc.url}" /> <property name="username"value="${jdbc.username}" /> <property name="password"value="${jdbc.password}" /> dataSource> environment> <environment id="test"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver"value="${jdbc.test.driver}" /> <property name="url"value="${jdbc.test.url}" /> <property name="username"value="${jdbc.test.user}" /> <property name="password"value="${jdbc.test.password}" /> dataSource> environment> environments>
大家可以看到再environments标签中有两个environment子标签,配置了两个数据库信息environments标签中的default属性,用于选择到底使用的是哪个环境。
transactionManager标签用于设置事务管理器用于MyBatis的事务管理。MyBatis 支持两种事务管理器类型:
JDBC需要再程序中使用通过SqlSession对象的commit()方法提交,通过rollback()方法回滚,默认情况下是需要手动提交的。
MANAGED由Mybatis来管理事务的整个事务的生命周期,默认情况下,每次操作都会关闭数据库连接。
dataSource标签主要用于配置应用的数据源连接方式和数据库连接信息,使用type属性来设置数据源类型:
UNPOOLED表示不使用数据库连接池,每次数据库操作Mybatis都需要创建一个Connection对象。
POOLED使用Mybatis提供的数据库连接池。
JNDI使用JNDI数据源(JNDI的方式一般是配置在应用服务器中)
Mapper 标签的作用就是通知Mybatis使用哪些mapper文件,一共有四种写法:
第一种:使用mapper文件的相对路径,这个路径是相对于classpath而言的。
<mapper resource="mapper/TravelRouteMapper.xml" />
第二种:使用url
<mapper url="D://xxx/xxxMapper.xml" />
第三种:使用类的名称空间,使用这种方式,需要满足以下几个规约:
1.Mapper文件名要与 Dao 接口名相同
2.Mapper文件要与接口在同一包中
3.Mapper文件中的 namespace 属性值为 Dao 接口的类的名称空间
<mapper class="com.pz.route.dao.TravelRouteDao" />
第四种:使用mybatis提供的动态代理实现(无需自己实现xxxDaoImpl)
使用这种方式,需要满足以下几个规约:
(1)dao 使用 mapper 的动态代理实现(后面再将)
(2)Mapper文件名与 Dao 接口名相同
(3)Mapper文件与接口放在同一包中
(4)Mapper文件中的 namespace 属性值为 Dao接口的类的名称空间
<mapper package="com.pz.route.dao" />
我们编写的第一个MyBatis程序,实现了一个向route数据库travel_route表中新增一条数据的功能,有一些比较值得注意的地方大家一起来看下:
<insert id="add"parameterType="com.pz.route.domain.TravelRoute"> INSERT INTO travel_route (travel_route_name,travel_route_price,travel_route_introduce,travel_route_flag,travel_route_date,isThemeTour,travel_route_count,travel_route_cid,travel_route_image,travel_route_seller_id) values(#{travelRouteName},#{travelRoutePrice},#{travelRouteIntroduce},#{travelRouteFlag},#{travelRouteDate},#{isThemeTour},#{travelRouteCount},#{travelRouteCid},#{travelRouteImage},#{travelRouteSellerId}) insert>
id:需要执行的SQL语句的唯一标识,实际上它mybatix被识别的方式是namespace+id,一个id可被用代表一段需要执行的sql语句,在一个mapper文件中对id值的要求是不能重复出现的。
parameterType:sql语句中参数的类型,实际上MyBatis利用反射机制感知dao接口的参数类型,不写这个配置也是可以的
#{ }:这个符号表示参数,支持el表达式,我们通过JavaBean的方式做参数传递,花括号里的值为JavaBean的属性,如果属性也是对象,支持对象名.方法名的形式。
Mybatis事务提交,我们看TravelRouteDaoImpl:
try { sqlSession = MyBatisUtil.getSqlSession(); //新增数据操作 sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute); //提交SqlSession sqlSession.commit(); //无需再做关闭,SqlSessionFactory会自动关闭sqlSession } catch (Exception e) { e.printStackTrace(); }
默认情况下Mybatis的事务是不会自动提交的,如果想自动提交事务,可以在MyBatisUtil中修改openSession的方法,传入参数true。如果不传入任何参数或者传入false,mybatis无法自动提交事务。
public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(true); }
如果不提交事务,数据是不会持久化到数据库的。
travel_route表,我们设置的主键是自增长类型,所以在编写SQL的时候,我们不用写入主键travel_route_id,如果我们想在插入数据后获取主键我们可以使用下面的方式:
<selectKey resultType="Long"keyProperty="travelRouteId" order="AFTER"> SELECT @@identity selectKey>
这样子在mybatis完成insert操作之后后,会将当前写入数据的ID查询出来,重写Travle的toString方法,再编写一段程序看下效果:
@Test public void testAddTravelRoute(){ TravelRouteDao travelRouteDao = new TravelRouteDaoImpl(); TravelRoute travelRoute = new TravelRoute(); travelRoute.setTravelRouteName("新增线路2"); travelRoute.setTravelRoutePrice(999d); travelRoute.setTravelRouteDate("2019-10-26"); travelRoute.setTravelRouteFlag(1); travelRoute.setIsThemeTour("1"); travelRoute.setTravelRouteCount(0); travelRoute.setTravelRouteCid(1); travelRoute.setTravelRouteIntroduce("双导游服务,免收服务小费,周全照顾贴心服务随心出游!品尝越南特色国宝美食,升级一餐越式炸鸡火锅宴!"); travelRoute.setTravelRouteImage("img/product/small/m3db4d2277b5df3d98597f79082ef92d6d.jpg"); travelRoute.setTravelRouteSellerId(1L); System.out.println("before insert==="); System.out.println(travelRoute); travelRouteDao.add(travelRoute); System.out.println("after insert==="); System.out.println(travelRoute); }
我们再在TravelRouteDaoImpl中commit打印增加两行打印代码
@Override public void add(TravelRoute travelRoute) { try { sqlSession = MyBatisUtil.getSqlSession(); //新增数据操作 sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute); System.out.println("before commit==="); System.out.println(travelRoute); //提交SqlSession sqlSession.commit(); //无需再做关闭,SqlSessionFactory会自动关闭sqlSession } catch (Exception e) { e.printStackTrace(); } }
运行程序后,我们可以清晰地看到,在commit之前,id已经有值了,实际上,当insert语句执行后,commit之前,主键id已经返回了数据,这个事务不管是commit或者是rollback,travel_route的主键已经使用掉一个了,再做insert操作mysql只会分配一个新的主键。由此我们得出一个结论:数据库主键的分配和是否提交无关,只要执行了insert操作,mysql就会分配一个主键。