mybatis的详解
最新不知道脑子怎么想的,突然对mybatis特别感兴趣,之前在学校的时候学过两天,有了一个简单的认识,工作以后,项目中也有用到,趁着兴趣还在,抓紧整理一个文档,方便学习mybatis,同时,自己也在巩固一下.
mybatis的简单介绍:
MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录。
Mybatis的功能架构分为三层(图片借用了百度百科):
1) API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
如图所示:
为什么要学习mybatis呢,首先我们来创建一个maven项目
如图所示:
这时mybatis的maven项目就创建好了.
开始在pom.xml中引入相关的jar包
<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>com.j1.mybatis</groupId> <artifactId>mybatis-demo</artifactId> <version>1.0.0-SNAPSHOT</version> <parent> <groupId>cn.j1.parent</groupId> <artifactId>j1-parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <!-- MySql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> <!-- Mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> </dependencies> </project>
这时项目会报一个小红叉,不要怕,如图所示:
先从JDBC程序开始:代码如下:
package com.j1.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class JdbcTest { public static void main(String[] args) { // 数据库连接 Connection con = null; // 执行sql ResultSet res = null; // 封装sql PreparedStatement pre = null; // 加载驱动 try { Class.forName("com.mysql.jdbc.Driver"); // 创建连接 // 创建连接 String url = "jdbc:mysql://127.0.0.1:3306/mybatis"; String username = "root"; String password = "root"; con = DriverManager.getConnection(url, username, password); // 获取PreparedStatement对象 String sql = "select * from tb_user u where u.user_name=?"; pre = con.prepareStatement(sql); // 封装查询的参数 pre.setString(1, "wangwu"); // 执行 res = pre.executeQuery(); // 打印结果集, while (res.next()) { System.out.println("username : " + res.getString("user_name")); System.out.println("name : " + res.getString("name")); System.out.println("age : " + res.getInt("age")); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { // 释放资源 if (pre != null) { pre.close(); } if (res != null) { res.close(); } if (con != null) { con.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
查出的结果是:
username : wangwu
name : 王五
age : 22
这个项目跑下来,我们发现很多问题:
1、 数据库连接字符串、用户名、密码等信息硬编码到java代码中,维护不方便,修改内容时需要重新编译java代码,解决方案:放置到外部配置文件中;
2、 sql语句硬编码到java代码中,问题:
a) 维护不方便
b) 代码重用性差
解决:假设:能否将sql配置到外部文件?
3、 手动设置参数:
a) 将参数值硬编码到java代码中
b) 判断参数类型,使用极其不便;
解决:能否自动设置参数?
4、 手动映射对象,问题:不方便使用,解决方案:能否自动映射?
5、 频繁的创建连接、关闭连接造成资源浪费,解决方案:使用连接池解决
这时,我们的主角开始上场了,很轻松的解决上面的问题,先写一个入门案列:
在开发时我们的开发习惯都是面向接口开发,由此,我们来写一个接口,
根据用户名查询数据,到这,先容我抽根烟,
package com.j1.jdbc.Model; import java.util.Date; public class User { private Long id; private String userName; private String password; private String name; private Integer age; private Boolean sex; private Date birthday; private Date created; private Date updated; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Boolean getSex() { return sex; } public void setSex(Boolean sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getUpdated() { return updated; } public void setUpdated(Date updated) { this.updated = updated; } @Override public String toString() { return "User [id=" + id + ", userName=" + userName + ", password=" + password + ", name=" + name + ", age=" + age + ", sex=" + sex + ", birthday=" + birthday + ", created=" + created + ", updated=" + updated + "]"; } } //我的第一个mybatisjava程序 package com.j1.mybatis; 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; import com.j1.jdbc.Model.User; public class Mybatis { public static void main(String[] args) { //讀取配置文件 try { //配置文件 String resource="mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream); //System.out.println(sqlSessionFactory); SqlSession session=sqlSessionFactory.openSession(); //System.out.println(session); User user=session.selectOne("com.j1.user.queryUserByUserName", "zhangsan"); System.out.println(user); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //UserMapper.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.j1.user"> <select id="queryUserByUserName" resultType="com.j1.jdbc.Model.User"> SELECT *, user_name userName FROM tb_user WHERE user_name = #{userName} </select> </mapper>
//加入一些配置文件:
#jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true jdbc.username=root jdbc.password=root jdbc.test.driver=com.mysql.jdbc.Driver jdbc.test.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true jdbc.test.username=root jdbc.test.password=root //mybatis 的主要配置文件 <?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> <!-- 引入外部的配置文件 --> <properties resource="jdbc.properties"/> <settings> <!-- 开启驼峰自动映射 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <!-- 设置别名 --> <!-- <typeAlias type="com.j1.Model.User" alias="user"/> --> <package name="com.j1.jdbc.Model"/> </typeAliases> <!-- 指定环境 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}" /> <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.username}" /> <property name="password" value="${jdbc.test.password}" /> </dataSource> </environment> --> </environments> <mappers> <!-- 指定UserMapper.xml --> <mapper resource="UserMapper.xml" /> <!-- <mapper resource="UserMapper2.xml" /> --> <!-- <mapper class="com.j1.mybatis.Iuser"/> --> <!-- 配置掃描包 --> <!-- <package name="com.j1.mybatis"/>--> </mappers> </configuration>
运行的结果是:
User [id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=true, birthday=Wed Aug 08 00:00:00 CST 1984, created=Fri Sep 19 16:56:04 CST 2014, updated=Sun Sep 21 11:24:59 CST 2014]
到这里,说明第一个mybatis程序就运行好了;
总结一下mybati的运行过程,如图所示:
如果运行过程有问题,可以参考我的另一篇文章,专门讲解mybatis的运行原理的,这里就不多说了;
接着,我们使用mybatis完成一个完整的增删改查的功能,
package com.j1.dao; import java.util.HashMap; import java.util.List; import org.apache.ibatis.annotations.Param; import com.j1.jdbc.Model.User; public interface IUser { /** * 根据用户名查询用户信息 * @param userName * @return */ public User queryUserByUserName(@Param("userName")String userName); /** * 新增用户数据 * * @param user */ public void saveUser(User user); /** * 更新用户数据U * @param user */ public void updateUser(User user); /** * 根据id删除用户数据 * @param id */ public void deleteUserById(Long id); /** * 通用查询实现 * * @param tableName * @return */ public List<HashMap<String, Object>> queryByTabkeName(String tableName); /** * 根据用户密码查询 * * @param userName * @param password * @return */ // public User queryUserByUserNameAndPassword(HashMap<String, Object> map); public User queryUserByUserNameAndPassword(@Param("userName")String userName, @Param("password")String password); } package com.j1.dao; import java.util.HashMap; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import com.j1.jdbc.Model.User; public class UserDao implements IUser { /** * 因为是先入门,暂时没有和Spring做集成, * 通过构造方法来获得SqlSessionFactory, */ SqlSessionFactory sqlSessionFactory=null; public UserDao ( SqlSessionFactory sqlSessionFactory){ this.sqlSessionFactory=sqlSessionFactory; } @Override public User queryUserByUserName(String userName) { SqlSession sqlSession=sqlSessionFactory.openSession(true);//默认是false,在进行insert,update之后需要commit,这里我们直接设为true return sqlSession.selectOne("com.j1.user.queryUserByUserName", userName); } @Override public void saveUser(User user) { SqlSession sqlSession = sqlSessionFactory.openSession(true); sqlSession.insert("com.j1.user.saveUser", user); } @Override public void updateUser(User user) { SqlSession sqlSession = sqlSessionFactory.openSession(true); sqlSession.update("com.j1.user.updateUser", user); } @Override public void deleteUserById(Long id) { SqlSession sqlSession = sqlSessionFactory.openSession(true); sqlSession.delete("com.j1.user.deleteUserById", id); } @Override public List<HashMap<String, Object>> queryByTabkeName(String tableName) { return null; } @Override public User queryUserByUserNameAndPassword(String userName, String password) { // TODO Auto-generated method stub return null; } } <?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.j1.user"> <select id="queryUserByUserName" resultType="com.j1.jdbc.Model.User"> SELECT *, user_name userName FROM tb_user WHERE user_name = #{userName} </select> <insert id="saveUser" parameterType="com.j1.jdbc.Model.User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO tb_user ( id, user_name, password, name, age, sex, birthday, created, updated ) VALUES ( NULL, #{userName}, #{password}, #{name}, #{age}, #{sex}, #{birthday}, NOW(), NOW() ); </insert> <update id="updateUser" parameterType="com.j1.jdbc.Model.User"> UPDATE tb_user SET password = #{password}, name = #{name}, age = #{age}, sex = #{sex}, birthday = #{birthday}, updated = NOW() WHERE id = #{id}; </update> <delete id="deleteUserById" parameterType="java.lang.Long"> DELETE FROM tb_user WHERE id = #{id} </delete> </mapper> package com.j1.test; import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; import java.util.Date; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.j1.dao.IUser; import com.j1.dao.UserDao; import com.j1.jdbc.Model.User; public class UserDAOTest { private static IUser userDAO = null; @Before public void init() { try { // 构造SqlSessionFactory // 定义配置文件路径 String resource = "mybatis-config.xml"; // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.userDAO = new UserDao(sqlSessionFactory); } catch (IOException e) { e.printStackTrace(); } } /** * 这里我们也可用静态代码块 */ static{ String resource = "mybatis-config.xml"; // 读取配置文件 InputStream inputStream; try { inputStream = Resources.getResourceAsStream(resource); // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); userDAO = new UserDao(sqlSessionFactory); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Test public void testQueryUserByUserName() { User user = this.userDAO.queryUserByUserName("zhangsan"); System.out.println(user); } @Test public void testSaveUser() { com.j1.jdbc.Model.User user = new User(); user.setAge(20); user.setBirthday(new Date()); user.setName("test_name_4"); user.setPassword("123456"); user.setSex(true); user.setUserName("test_username_4"); this.userDAO.saveUser(user); System.out.println(user); } @Test public void testUpdateUser() { User user = new User(); user.setAge(20); user.setBirthday(new Date()); user.setName("xiaobizaizi"); user.setPassword("123qwe"); user.setSex(true); user.setId(6L); this.userDAO.updateUser(user); } @Test public void testDeleteUserById() { this.userDAO.deleteUserById(6L); } }
//测试通过了
总结:
1 ,定义IUser接口
2,定义Iuser的实现类
3, 编写Mapper.xml文件
4,编写测试用例
现在我再来看这个代码,发现还是有问题
1.1. 接口实现的问题
1、 每次都要openSession,能否自动open
2、 CRUD的实现非常的相像?
有没有一种简便的办法实现接口的实现类?
这就引出动态代理的方法来解决这些问题,只定义接口,不书写实现类,(动态代理后面我会写个博客专门来介绍):
package com.j1.test; import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; import java.util.Date; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.j1.dao.IUser; import com.j1.dao.UserDao; import com.j1.jdbc.Model.User; public class UserDAOTest { private static IUser userDAO = null; @Before public void init() { try { // 构造SqlSessionFactory // 定义配置文件路径 String resource = "mybatis-config.xml"; // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // this.userDAO = new UserDao(sqlSessionFactory); this.userDAO = sqlSessionFactory.openSession(true).getMapper(IUser.class); } catch (IOException e) { e.printStackTrace(); } } /** * 这里我们也可用静态代码块 */ static{ String resource = "mybatis-config.xml"; // 读取配置文件 InputStream inputStream; try { inputStream = Resources.getResourceAsStream(resource); // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); userDAO = new UserDao(sqlSessionFactory); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Test public void testQueryUserByUserName() { User user = this.userDAO.queryUserByUserName("zhangsan"); System.out.println(user); } @Test public void testSaveUser() { com.j1.jdbc.Model.User user = new User(); user.setAge(20); user.setBirthday(new Date()); user.setName("test_name_4"); user.setPassword("123456"); user.setSex(true); user.setUserName("test_username_4"); this.userDAO.saveUser(user); System.out.println(user); } @Test public void testUpdateUser() { User user = new User(); user.setAge(20); user.setBirthday(new Date()); user.setName("test_name_1"); user.setPassword("123qwe"); user.setSex(true); user.setId(6L); this.userDAO.updateUser(user); } @Test public void testDeleteUserById() { this.userDAO.deleteUserById(6L); } }
1.1. 使用Mybatis提供的动态代理实现类实现CRUD
1、 定义接口
2、 定义Mapper.xml,注意:
a) namespace必须是接口的全路径,如:cn.j1.mybatis.dao.Iuser
b) Statement的id必须要接口中的方法名一致
获取动态代理实现类:
总结:
如果手动实现接口实现类,namespace和Statement的id随便定义;
如果使用动态代理实现类,namespace和Statement必须按照约定来定义;
使用mapper接口不用写接口实现类即可完成数据库操作,使用非常简单,也是官方所推荐的使用方法。
使用mapper接口的必须具备以几个条件:
1) Mapper的namespace必须和mapper接口的全路径一致。
2) Mapper接口的方法名必须和sql定义的id一致。
3) Mapper接口中方法的输入参数类型必须和sql定义的parameterType一致。
4) Mapper接口中方法的输出参数类型必须和sql定义的resultType一致。
至此,mybatis的第一天内容就讲的差不多了,下面讲一下mybatis-config.xml的一些细节:
<!-- 引入外部的配置文件 --> <properties resource="jdbc.properties"/> <settings> <!-- 开启驼峰自动映射 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <!-- 设置别名 --> <!-- <typeAlias type="com.j1.Model.User" alias="user"/> --> <package name="com.j1.jdbc.Model"/> </typeAliases> <!-- 指定环境 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}" /> <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.username}" /> <property name="password" value="${jdbc.test.password}" /> </dataSource> </environment> <mappers> <!-- <mapper resource="UserMapper.xml" /> --> <!-- <mapper resource="UserMapper2.xml" /> --> <!-- <mapper class="com.j1.mybatis.Iuser"/> --> <!-- 配置掃描包 --> <package name="com.j1.dao"/> </mappers> //注意,mybatis的配置是有顺序的,不能乱来
此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
简单的mybatis就算是入门了,有时间吧mybatis的一些稍微高深点的东西整理一下,与Spring进行整合,动态sql的拼接,mybatis的缓存机制,mybatis的高级查询等等