MyBatis框架浅析之 初识MyBatis
更详细的内容可以查看 官方给出的文档 : http://www.mybatis.org/mybatis-3/zh/getting-started.html
一.重要的SqlSessionFactory对象
见名知意, SqlSessionFactory是生产 SqlSession的工厂, 而执行SQL语句必须要通过 SqlSession对象 ,因此获取SqlSessionFactory 是第一步
根据SqlSessionFactoryBuilder类中的 实例方法 build() 一般使用下图中的两种方式重载方法构建SqlSessionFactory对象, 1种是通过接收InputStream 刘对象
另一种是通过接收Configuration对象. 通常更多的是创建配置文件,使用第一种方式,即传入InputStream对象
获取SqlSessionFactory对象的方式共两种:
/**** 方式一: 从 XML 中构建 SqlSessionFactory ****/ String resource = "mybatis_config.xml"; // 加载mybatis的配置文件(它也加载关联的映射文件) InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } // 构建sqlSession的工厂 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
附上项目结构:
附上供于本地调试的POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>mybatis_study</groupId> <artifactId>mybatis_study</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> </dependencies> </project>
附上 mybatis的 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> <!--为类的权限定名称设置简短的别名--> <typeAliases> <typeAlias type="com.baoxian.domain.UserInfo" alias="UserInfo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!--事务管理类型--> <dataSource type="POOLED"> <property name="username" value="root"/> <property name="password" value=""/> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/crm?serverTimezone=GMT%2B8"/> </dataSource> </environment> </environments> <!--存放处理SQL语句的XML文件--> <mappers> <mapper resource="com/baoxian/dao/UserInfoMapper.xml"/> <mapper resource="com/baoxian/dao/ProductMapper.xml"/> </mappers> </configuration>
/**** 方式二: 不使用 XML 构建 SqlSessionFactory ****/ DataSource dataSource = BlogDataSourceFactory.getBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
//此种方式没有写过测试, 详细细节待补充;对此种构建方式有兴趣的小伙伴可以自行查阅资料(但一般都是使用第一种方式)
二.通过SqlSeesionFactory 创建 SqlSession对象
//创建SQLSession /* sqlSession默认是不自动提交事务的,所以insert,delete ,update 默认是不会生效的 开启自动提交事务的两种解决方案: 1.openSession(true); 2.sqlSession.commit();手动提交事务 */ SqlSession sqlSession = sessionFactory.openSession(true);
三 .通过SqlSession对象执行一条简单的select 语句:
执行select语句的方式同样有两种:
方式一:直接通过SqlSession对象中封装的实例方法,执行SQL语句
@Test public void testSelectOne() { try{ UserInfo user = sqlSession.selectOne("com.baoxian.dao.UserInfoMapper.selectUserById", 1L); System.out.println(user); }catch (Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } }
附上 UserInfo 的实例类:
package com.baoxian.domain; public class UserInfo { private long id; private String userName; private int age; public long getId() { return id; } public String getUserName() { return userName; } public int getAge() { return age; } public void setId(long id) { this.id = id; } public void setUserName(String userName) { this.userName = userName; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "UserInfo{" + "id=" + id + ", name='" + userName + '\'' + ", age=" + age + '}'; } }
附上userInfo的表设计:
方式二:通过SqlSession获取 Mapper接口对象,再通过接口对象调用接口中的方法(接口中的方法名称,返回值,形参要与Mapper.xml文件中保持一致)
相比方式一: 此种方式更加直观,而且不用传入较长的xml文件路径 ,避免了代码的写死 ,因此推荐方式二
@Test public void testSelectOne2() { try{ UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class); UserInfo userInfo = mapper.selectUserById(1L); System.out.println(userInfo); }catch (Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } }
附上UserInfoMapper接口的代码:
package com.baoxian.dao; import com.baoxian.domain.UserInfo; import java.util.List; public interface UserInfoMapper { //查询全部User public List<UserInfo> selectAll(); //根据user的ID查询User对象 public UserInfo selectUserById(Long id); }
附上UserInfoMapper.xml文件的代码:
对于初学者来说,xml中的标签可能不是太理解,后面会再做详细介绍:
<?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.baoxian.dao.UserInfoMapper" > <!-- 正常情况下 type 需要写上实体类的全限定名称,但是由于在 mybatis的配置文件中,即 mybatis_config.xml中指定了别名,所以可以使用别名的方式 <resultMap id="BaseResultMap" type="com.baoxian.domain.UserInfo" > --> <resultMap id="BaseResultMap" type="UserInfo" > <id column="id" property="id" /> <result column="username" property="userName" /> <result column="age" property="age" /> </resultMap> <!--通过ID删除用户--> <delete id="deleteByPrimaryKey" parameterType="java.lang.Long" > delete from UserInfo where id = #{id} </delete> <!--新增用户--> <insert id="insert" parameterType="com.baoxian.domain.UserInfo" useGeneratedKeys="true" keyProperty="id" > insert into UserInfo (username,age) values (#{userName},#{age}) </insert> <!--通过ID更新用户--> <update id="updateByPrimaryKey" parameterType="com.baoxian.domain.UserInfo" > update UserInfo set username = #{userName},age =#{age} where id = #{id} </update> <!--通过ID查找用户--> <select id="selectUserById" resultMap="BaseResultMap" parameterType="java.lang.Long" > select id,userName,age from UserInfo where id = #{id} </select> <!--查询出所有用户--> <select id="selectAll" resultMap="BaseResultMap" > select id, username,age from userinfo </select> </mapper>
四.品位已映射的SQL语句
从上述例子可知,所有的SQL都可以写进相应的XML映射文件中,简单介绍 xml文件中 <mapper>标签 与 <select>标签
<?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.baoxian.dao.UserInfoMapper" > <!--通过ID查找用户--> <select id="selectUserById" resultMap="BaseResultMap" parameterType="java.lang.Long" > select id,userName,age from UserInfo where id = #{id} </select> </mapper>
在上述例子中,通过sqlSession执行select语句:
UserInfo user = sqlSession.selectOne("com.baoxian.dao.UserInfoMapper.selectUserById", 1L);
selectOne方法的第一个参数:com.baoxian.dao.UserInfoMapper.selectUserById , 有点类似于 java中, 使用全限定名调用类中的方法
定位到这条select的语句的流程是 :①根据nameSpace找到对应的映射文件 ②根据要执行的sql语句id 找到相应的标签
所以要保证每个映射文件的 nameSpace不同,即唯一,同时保证每个映射文件中的 id 不同
通过Mapper实例执行SQL语句:
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class); UserInfo userInfo = mapper.selectUserById(1L);
此方式,将UserInfoMapper 接口的全限定名 寻找nameSpace 与 该接口全限定名相同的 映射文件. 如果找不到则会报错. 因此一般将nameSpace设置为XxxMapper接口的全限定名
而接口中的方法名 则 与该映射文件中 ,select, insert, update,delete 标签的 ID 进行匹配,
方法中的参数类型 与 该标签中的parameterType对应.
方法的返回值类型 则与 resultType 或 resultMap 对应
方法中形参的名称与sql语句中动态参数名称需相同 ,获取参数值的方式是通过 #{参数名称 或 属性名称} 这种形式获取
除了可以通过配置XML 执行sql语句外, 还可以通过在Mapper接口中使用注解达到相同的效果,例如:
//根据user的ID查询User对象 @Select("select * from UserInfo where id = #{id}") public UserInfo queryUserById(Long id);
此效果就相当于映射文件中新增了一下代码:
<!--通过ID查找用户--> <select id="queryUserById" resultMap="BaseResultMap" parameterType="java.lang.Long" > select * from userInfo where id = #{id} </select>
但是不能既使用@select 注解的方式 ,又在映射文件中 加入select 标签的方式 ,否则会运行出错:
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.baoxian.dao.UserInfoMapper.queryUserById
五. 作用域问题
SqlSessionFactoryBuilder 建议方法作用域
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。
SqlSessionFactory : 建议单例模式 应用作用域
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession 建议作用域:方法
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); }
映射器实例(Mapper Instances)即Mapper接口实例 建议作用域: 方法
映射器是一个你创建来绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 SqlSession 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可废弃。并不需要显式地关闭映射器实例,尽管在整个请求作用域(request scope)保持映射器实例也不会有什么问题,但是很快你会发现,像 SqlSession 一样,在这个作用域上管理太多的资源的话会难于控制。所以要保持简单,最好把映射器放在方法作用域(method scope)内。下面的示例就展示了这个实践:
SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); // do work } finally { session.close(); }