Mybatis系列(一):Mybatis入门
一、Mybatis是什么
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,mybatis是支持普通sql查询,存储过程和高级映射的优秀的、轻量级的持久层框架。
二、Mybatis的作用
1. mybatis支持普通sql查询,存储过程和高级映射。
2. mybatis消除了几乎所有的jdbc代码和参数的手工设置以及结果集的检索。
3. mybatis使用简单的xml或注解用于配置和原始映射,将简单javabean映射成数据库中的对象。
三、为什么要学习Mybatis
1. 我们先来看使用jdbc操作数据库的过程
1 package com.study.spring.mybatistest; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 9 /** 10 * jdbc操作数据库 11 * @author THINKPAD 12 * 13 */ 14 public class JdbcTest { 15 public static void main(String[] args) { 16 17 // 数据库连接 18 Connection connection = null; 19 // 预编译的Statement,使用预编译的Statement提高数据库性能 20 PreparedStatement preparedStatement = null; 21 // 结果 集 22 ResultSet resultSet = null; 23 24 try { 25 // 加载数据库驱动 26 Class.forName("com.mysql.jdbc.Driver"); 27 28 // 通过驱动管理类获取数据库链接 29 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/study?characterEncoding=utf-8", 30 "root", "root"); 31 // 定义sql语句 ?表示占位符 32 String sql = "select * from t_order where order_no = ?"; 33 // 获取预处理statement 34 preparedStatement = connection.prepareStatement(sql); 35 // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 36 preparedStatement.setString(1, "123"); 37 // 向数据库发出sql执行查询,查询出结果集 38 resultSet = preparedStatement.executeQuery(); 39 // 遍历查询结果集 40 while (resultSet.next()) { 41 System.out.println(resultSet.getString("id") + " " + resultSet.getString("orderinfo")); 42 } 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } finally { 46 // 释放资源 47 if (resultSet != null) { 48 try { 49 resultSet.close(); 50 } catch (SQLException e) { 51 e.printStackTrace(); 52 } 53 } 54 if (preparedStatement != null) { 55 try { 56 preparedStatement.close(); 57 } catch (SQLException e) { 58 e.printStackTrace(); 59 } 60 } 61 if (connection != null) { 62 try { 63 connection.close(); 64 } catch (SQLException e) { 65 e.printStackTrace(); 66 } 67 } 68 69 } 70 71 } 72 }
使用jdbc操作数据库存在如下的问题:
1.1、数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。
Mybatis解决方案:使用数据库连接池管理数据库连接。
1.2、将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
Mybatis解决方案:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。
1.3、向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。
Mybatis解决方案:将sql语句及占位符号和参数全部配置在xml中。
1.4、从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。
Mybatis解决方案:将查询的结果集,自动映射成java对象。
四、Mybatis架构图
架构图的说明:
1、mybatis配置
1.1 SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
1.2 mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
五、Mybatis入门程序
1. 需求:创建一个用户表,用户名有用户名称,生日,性别,地址字段,对用户表进行CRUD操作
2. 环境:JDK1.8,eclipse,Mysql5.6,maven
3. 创建一个名为MybatisTest的工程,目录结构如下:
4. 在数据库里面创建用户表,并插入数据
1 SET FOREIGN_KEY_CHECKS=0; 2 3 -- ---------------------------- 4 -- Table structure for t_user 5 -- ---------------------------- 6 DROP TABLE IF EXISTS `t_user`; 7 CREATE TABLE `t_user` ( 8 `id` int(11) NOT NULL AUTO_INCREMENT, 9 `username` varchar(30) NOT NULL COMMENT '用户名称', 10 `birthday` date DEFAULT NULL COMMENT '生日', 11 `sex` char(2) DEFAULT NULL COMMENT '性别', 12 `address` varchar(256) DEFAULT NULL COMMENT '地址', 13 PRIMARY KEY (`id`) 14 ) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; 15 16 -- ---------------------------- 17 -- Records of t_user 18 -- ---------------------------- 19 INSERT INTO `t_user` VALUES ('1', 'a', '2017-10-12', '2', '深圳'); 20 INSERT INTO `t_user` VALUES ('2', 'b', '2017-10-12', '2', '深圳'); 21 INSERT INTO `t_user` VALUES ('3', 'c', '2017-10-12', '1', '深圳'); 22 INSERT INTO `t_user` VALUES ('4', 'd', '2017-10-12', '2', '深圳');
5. 在工程里面创建用户表的实体类UserModel.java
1 package com.study.mybatis.model; 2 3 import java.util.Date; 4 5 /** 6 * 用户model 和数据库的表t_user对应 7 * 8 * @author THINKPAD 9 * 10 */ 11 public class UserModel { 12 // 属性名称和数据库字段名称保持一致 13 private Integer id; 14 // 姓名 15 private String username; 16 // 性别 17 private String sex; 18 // 地址 19 private String address; 20 // 生日 21 private Date birthday; 22 23 public Integer getId() { 24 return id; 25 } 26 27 public void setId(Integer id) { 28 this.id = id; 29 } 30 31 public String getUsername() { 32 return username; 33 } 34 35 public void setUsername(String username) { 36 this.username = username; 37 } 38 39 public String getSex() { 40 return sex; 41 } 42 43 public void setSex(String sex) { 44 this.sex = sex; 45 } 46 47 public String getAddress() { 48 return address; 49 } 50 51 public void setAddress(String address) { 52 this.address = address; 53 } 54 55 public Date getBirthday() { 56 return birthday; 57 } 58 59 public void setBirthday(Date birthday) { 60 this.birthday = birthday; 61 } 62 63 @Override 64 public String toString() { 65 return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", address=" + address + ", birthday=" 66 + birthday + "]"; 67 } 68 69 }
6. 定义实体类在mybatis里面的映射文件mapper-user.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <!-- namespace命名空间,作用就是对sql进行分类化的管理,理解为sql隔离 6 注意:使用mapper代理开发时,namespace有特殊作用 7 --> 8 <mapper namespace="test"> 9 <!-- 在映射文件中配置很多sql语句 --> 10 <!-- 需求:通过Id查询用户表的记录 --> 11 <!-- 通过SELECT执行数据库查询 12 id:标识映射文件中的sql,称为statement的id; 13 将sql语句封装在mapperStatement的对象中,所以Id称为Statement的id; 14 parameterType:指定输入参数的类型,这里指定int型 15 #{}:表示一个占位符; 16 #{id}:其中Id表示接收输入的参数,参数名称就是Id,如果输入参数是简单类型,#{}中的参数名可以任意,可以是value或者其它名称; 17 resultType:指定sql输出结果所映射的java对象类型,select指定resultType表示将单条记录映射成java对象。 18 --> 19 <select id="findUserById" parameterType="int" resultType="com.study.mybatis.model.UserModel" > 20 select * from t_user where id=#{id} 21 </select> 22 <!-- 根据用户名称模糊查询用户信息,可能返回多条数据 23 resultType:指定的就是单条记录所映射的java类型; 24 ${}:表示拼接sql字符串,将接收到的参数内容不加任何修饰拼接在sql中. 25 使用${}拼接sql,可能会引起sql注入 26 ${value}:接收输入参数的内容,如果传入的是简单类型,${}中只能使用value 27 --> 28 <select id="findUserByName" parameterType="java.lang.String" resultType="com.study.mybatis.model.UserModel" > 29 select * from t_user where username LIKE '%${value}%' 30 </select> 31 <!-- 添加用户 32 parameterType:指定输入的参数类型是pojo(包括用户信息); 33 #{}中指定pojo的属性名称,接收到pojo对象的属性值 ,mybatis通过OGNL(类似struts2的OGNL)获取对象的属性值 34 --> 35 <insert id="insertUser" parameterType="com.study.mybatis.model.UserModel" > 36 <!-- 37 将insert插入的数据的主键返回到User对象中; 38 select last_insert_id():得到刚insert进去记录的主键值,只适用于自增主键; 39 keyProperty:将查询到的主键值,设置到parameterType指定的对象的那个属性 40 order:select last_insert_id()执行顺序,相对于insert语句来说它的执行顺序。 41 resultType:指定select last_insert_id()的结果类型; 42 --> 43 <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> 44 select last_insert_id() 45 </selectKey> 46 <!-- 47 使用mysql的uuid(),实现非自增主键的返回。 48 执行过程:通过uuid()得到主键,将主键设置到user对象的Id的属性中,其次,在insert执行时,从user对象中取出Id属性值; 49 <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> 50 select uuid() 51 </selectKey> 52 insert into t_user (id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) 53 --> 54 insert into t_user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) 55 </insert> 56 <!-- 删除用户 57 根据ID删除用户,需要输入Id值 58 --> 59 <delete id="deleteUser" parameterType="java.lang.Integer"> 60 delete from t_user where id=#{id} 61 </delete> 62 <!-- 更新用户 63 需要传入用户的Id和用户的更新信息 64 parameterType:指定User对象,包括Id和用户的更新信息,注意:Id是必须存在的 65 #{id}:从输入的User对象中获取Id的属性值 66 --> 67 <update id="updateUser" parameterType="com.study.mybatis.model.UserModel"> 68 update t_user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} 69 where id=#{id} 70 </update> 71 72 </mapper>
7. 定义mybatis的主配置文件SqlMapConfig.xml,在里面引入数据库配置文件db.properties和实体映射文件mapper-user.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <!-- 引用db.properties配置文件 --> 7 <properties resource="db.properties" /> 8 9 <!-- development : 开发模式 work : 工作模式,和spring整合后 environments配置将废除 --> 10 <environments default="development"> 11 <environment id="development"> 12 <!-- 使用jdbc事务管理,事务控制由mybatis管理 --> 13 <transactionManager type="JDBC" /> 14 <!-- 数据库连接池,由mybatis管理 --> 15 <dataSource type="POOLED"> 16 <!-- value属性值引用db.properties配置文件中配置的值 --> 17 <property name="driver" value="${driver}" /> 18 <property name="url" value="${url}" /> 19 <property name="username" value="${name}" /> 20 <property name="password" value="${password}" /> 21 </dataSource> 22 </environment> 23 </environments> 24 <!-- 加载映射文件 --> 25 <mappers> 26 <mapper resource="sqlmapper/mapper-user.xml" /> 27 </mappers> 28 </configuration>
8. db.properties
1 driver=com.mysql.jdbc.Driver 2 url=jdbc:mysql://192.168.152.1:3306/study?characterEncoding=utf-8 3 name=root 4 password=123456
9. 编写mybatis的测试类
1 package com.study.mybatis.mybatistest; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.util.Date; 6 import java.util.List; 7 8 import org.apache.ibatis.io.Resources; 9 import org.apache.ibatis.session.SqlSession; 10 import org.apache.ibatis.session.SqlSessionFactory; 11 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 12 import org.junit.Test; 13 14 import com.study.mybatis.model.UserModel; 15 16 /** 17 * Mybatis 测试 18 * 19 * @author THINKPAD 20 * 21 */ 22 public class MybatisTest { 23 // 根据Id查询用户信息,得到一条记录结果 24 @Test 25 public void findUserByIdTest() { 26 // mybatis的配置文件 27 String resource = "SqlMapConfig.xml"; 28 InputStream inputStream = null; 29 SqlSession sqlSession = null; 30 try { 31 inputStream = Resources.getResourceAsStream(resource); 32 // 1.创建会话工场,传入mybatis的配置文件信息 33 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 34 35 // 2.通过工厂得到SqlSession 36 sqlSession = sqlSessionFactory.openSession(); 37 38 // 3.通过sqlSession操作数据库 39 // 第一个参数:映射文件中的statement的Id,等于namespace + "." + statement的id; 40 // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数; 41 // sqlSession.selectOne结果是与映射文件所匹配的resultType类型的对象; 42 // selectOne:查询一条结果 43 UserModel user = sqlSession.selectOne("test.findUserById", 1); 44 System.out.println(user.toString()); 45 46 } catch (IOException e) { 47 e.printStackTrace(); 48 } finally { 49 if (sqlSession != null) { 50 sqlSession.close(); 51 } 52 if (inputStream != null) { 53 try { 54 inputStream.close(); 55 } catch (IOException e) { 56 e.printStackTrace(); 57 } 58 } 59 } 60 } 61 62 // 根据姓名模糊查询用户信息,得到一条或多条记录结果 63 @Test 64 public void findUserByNameTest() { 65 // mybatis的配置文件 66 String resource = "SqlMapConfig.xml"; 67 InputStream inputStream = null; 68 SqlSession sqlSession = null; 69 try { 70 inputStream = Resources.getResourceAsStream(resource); 71 // 1.创建会话工场,传入mybatis的配置文件信息 72 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 73 74 // 2.通过工厂得到SqlSession 75 sqlSession = sqlSessionFactory.openSession(); 76 77 // 3.通过sqlSession操作数据库 78 // 第一个参数:映射文件中的statement的Id,等于namespace + "." + statement的id; 79 // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数; 80 // sqlSession.selectOne结果是与映射文件所匹配的resultType类型的对象; 81 // list中的user和resultType类型一致 82 List<UserModel> list = sqlSession.selectList("test.findUserByName", "a"); 83 System.out.println(list); 84 85 } catch (IOException e) { 86 e.printStackTrace(); 87 } finally { 88 if (sqlSession != null) { 89 sqlSession.close(); 90 } 91 if (inputStream != null) { 92 try { 93 inputStream.close(); 94 } catch (IOException e) { 95 e.printStackTrace(); 96 } 97 } 98 } 99 } 100 101 // 添加用户 102 @Test 103 public void insertUserTest() { 104 // mybatis的配置文件 105 String resource = "SqlMapConfig.xml"; 106 InputStream inputStream = null; 107 SqlSession sqlSession = null; 108 try { 109 inputStream = Resources.getResourceAsStream(resource); 110 // 1.创建会话工场,传入mybatis的配置文件信息 111 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 112 // 2.通过工厂得到SqlSession 113 sqlSession = sqlSessionFactory.openSession(); 114 // 插入用户的对象 115 UserModel user = new UserModel(); 116 user.setUsername("d"); 117 user.setBirthday(new Date()); 118 user.setSex("1"); 119 user.setAddress("上海"); 120 // 3.通过sqlSession操作数据库 121 // 第一个参数:映射文件中的statement的Id,等于namespace + "." + statement的id; 122 // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数; 123 // sqlSession.selectOne结果是与映射文件所匹配的resultType类型的对象; 124 sqlSession.insert("test.insertUser", user); 125 // 执行提交事务 126 sqlSession.commit(); 127 128 // 项目中经常需要 获取新增的用户的主键 129 System.out.println(user.getId()); 130 131 } catch (IOException e) { 132 e.printStackTrace(); 133 } finally { 134 if (sqlSession != null) { 135 sqlSession.close(); 136 } 137 if (inputStream != null) { 138 try { 139 inputStream.close(); 140 } catch (IOException e) { 141 e.printStackTrace(); 142 } 143 } 144 } 145 } 146 147 // 根据Id删除用户 148 @Test 149 public void deleteUserTest() { 150 // mybatis的配置文件 151 String resource = "SqlMapConfig.xml"; 152 InputStream inputStream = null; 153 SqlSession sqlSession = null; 154 try { 155 inputStream = Resources.getResourceAsStream(resource); 156 // 1.创建会话工场,传入mybatis的配置文件信息 157 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 158 // 2.通过工厂得到SqlSession 159 sqlSession = sqlSessionFactory.openSession(); 160 // 3.通过sqlSession操作数据库 161 // 第一个参数:映射文件中的statement的Id,等于namespace + "." + statement的id; 162 // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数; 163 // sqlSession.selectOne结果是与映射文件所匹配的resultType类型的对象; 164 // 传入Id,删除用户 165 sqlSession.delete("test.deleteUser", 7); 166 // 执行提交事务 167 sqlSession.commit(); 168 } catch (IOException e) { 169 e.printStackTrace(); 170 } finally { 171 if (sqlSession != null) { 172 sqlSession.close(); 173 } 174 if (inputStream != null) { 175 try { 176 inputStream.close(); 177 } catch (IOException e) { 178 e.printStackTrace(); 179 } 180 } 181 } 182 } 183 184 // 根据Id更新用户信息 185 @Test 186 public void updateUserTest() { 187 // mybatis的配置文件 188 String resource = "SqlMapConfig.xml"; 189 InputStream inputStream = null; 190 SqlSession sqlSession = null; 191 try { 192 inputStream = Resources.getResourceAsStream(resource); 193 // 1.创建会话工场,传入mybatis的配置文件信息 194 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 195 // 2.通过工厂得到SqlSession 196 sqlSession = sqlSessionFactory.openSession(); 197 // 更新用户的信息 198 UserModel user = new UserModel(); 199 user.setId(2); 200 user.setUsername("e"); 201 user.setBirthday(new Date()); 202 user.setSex("2"); 203 user.setAddress("上海"); 204 // 3.通过sqlSession操作数据库 205 // 第一个参数:映射文件中的statement的Id,等于namespace + "." + statement的id; 206 // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数; 207 // sqlSession.selectOne结果是与映射文件所匹配的resultType类型的对象; 208 // 更具Id更新用户 209 sqlSession.update("test.updateUser", user); 210 // 执行提交事务 211 sqlSession.commit(); 212 } catch (IOException e) { 213 e.printStackTrace(); 214 } finally { 215 if (sqlSession != null) { 216 sqlSession.close(); 217 } 218 if (inputStream != null) { 219 try { 220 inputStream.close(); 221 } catch (IOException e) { 222 e.printStackTrace(); 223 } 224 } 225 } 226 } 227 228 public static void main(String[] args) { 229 // new MybatisTest().findUserByIdTest(); 230 // new MybatisTest().findUserByNameTest(); 231 // new MybatisTest().insertUserTest(); 232 // new MybatisTest().deleteUserTest(); 233 new MybatisTest().updateUserTest(); 234 } 235 }
六、mybatis和Hibernate的本质区别与应用场景
mybatis:专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全 的ORM框架,虽然程序员自己写sql,mybatis 也可以实现映射(输入映射、输出映射)。
应用场景:
适用与需求变化较多的项目,比如:互联网项目。
hibernate:是一个标准ORM框架(对象关系映射),入门门槛较高的,不需要程序写sql,sql语句自动生成了,对sql语句进行优化、修改比较困难的。
应用场景:
适用与需求变化不多的中小型项目,比如:后台管理系统,erp、orm、oa。。
七、小结
1. parameterType和resultType
parameterType:在映射文件中通过parameterType指定输入参数的类型。
resultType:在映射文件中通过resultType指定输出结果的类型
2.#{}和${}
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap;
如果接收简单类型,#{}中可以写成value或其它名称;
#{}接收pojo对象值,通过OGNL读取对象中的属性值,和struts的OGNL类似。
${}表示一个拼接符号,会引用sql注入,所以不建议使用{};
${}接收输入参数,类型可以是简单类型,pojo、hashmap;
如果接收简单类型,${}中只能写成value;
${}接收pojo对象值,通过OGNL读取对象中的属性值,和struts的OGNL类似。
3.selectOne()和selectList()
selectOne表示查询出一条记录进行映射。如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象)。
selectList表示查询出一个列表(多条记录)进行映射。如果使用selectList查询多条记录,不能使用selectOne。
如果使用selectOne报错: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4