介绍Mybatis之前,我们可以先看一段传统的数据库连接代码:
1 public class JdbcTest { 2 3 public static void main(String[] args) { 4 Connection connection = null; 5 //PreparedStatement是预编译的Statement,通过Statement发起数据库的操作 6 //PreparedStatement防止sql注入,执行数据库效率高 7 PreparedStatement preparedStatement = null; 8 ResultSet resultSet = null; 9 10 try { 11 //加载数据库驱动 12 Class.forName("com.mysql.jdbc.Driver"); 13 14 //通过驱动管理类获取数据库链接 15 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "1234"); 16 //定义sql语句 ?表示占位符 17 String sql = "select * from user where username = ?" ; 18 //获取预处理statement 19 preparedStatement = connection.prepareStatement(sql); 20 //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 21 preparedStatement.setString(1, "王五"); 22 //向数据库发出sql执行查询,查询出结果集 23 resultSet = preparedStatement.executeQuery(); 24 //遍历查询结果集 25 while(resultSet.next()){ 26 System.out.println(resultSet.getString("id")+" "+resultSet.getString("username")); 27 } 28 } catch (Exception e) { 29 e.printStackTrace(); 30 }finally{ 31 //释放资源 32 if(resultSet!=null){ 33 try { 34 resultSet.close(); 35 } catch (SQLException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 } 40 if(preparedStatement!=null){ 41 try { 42 preparedStatement.close(); 43 } catch (SQLException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 } 48 if(connection!=null){ 49 try { 50 connection.close(); 51 } catch (SQLException e) { 52 // TODO Auto-generated catch block 53 e.printStackTrace(); 54 } 55 } 56 } 57 } 58 }
通过阅读代码可以总结出的几个问题。
1、数据库连接频繁的创建和关闭,缺点浪费数据库的资源,影响操作效率
设想:使用数据库连接池
2、sql语句是硬编码,如果需求变更需要修改sql,就需要修改java代码,需要重新编译,系统不易维护。
设想:将sql语句 统一配置在文件中,修改sql不需要修改java代码。
3、通过preparedStatement向占位符设置参数,存在硬编码( 参数位置,参数)问题。系统不易维护。
设想:将sql中的占位符及对应的参数类型配置在配置文件中,能够自动输入 映射。
4、遍历查询结果集存在硬编码(列名)。
设想:自动进行sql查询结果向java对象的映射(输出映射)。
问题总结之后正式进入Mybatis.....
1.1 mybatis介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。 目前mybatis在github上托管。git(分布式版本控制,当前比较流程)
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
1.2 mybatis架构
下面以mapper代理的方式来编写一个简单的增删查改
SqlMapConfig.xml:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接,解决问题1
1 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 2 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 3 <configuration> 4 5 <!-- 属性定义 加载一个properties文件 在 properties标签 中配置属性值 --> 6 <properties resource="jdbc.properties" /> 7 8 <!-- 定义别名 --> 9 <typeAliases> 10 <!-- 单个别名的定义 alias:别名,type:别名映射的类型 --> 11 <!--<typeAlias type="com.emuii.mybatis.pojo.User" alias="user" />--> 12 <!-- 批量别名定义 指定包路径,自动扫描包下边的pojo,定义别名,别名默认为类名(首字母小写或大写) --> 13 <package name="com.emuii.mybatis.pojo" /> 14 </typeAliases> 15 16 <!-- 和spring整合后 environments配置将废除 --> 17 <environments default="development"> 18 <environment id="development"> 19 <!-- 使用jdbc事务管理 --> 20 <transactionManager type="JDBC" /> 21 <!-- 数据库连接池 --> 22 <dataSource type="POOLED"> 23 <property name="driver" value="${jdbc.driver}" /> 24 <property name="url" value="${jdbc.url}" /> 25 <property name="username" value="${jdbc.username}" /> 26 <property name="password" value="${jdbc.password}" /> 27 </dataSource> 28 </environment> 29 </environments> 30 31 <!-- 加载mapper映射 如果将和spring整合后,可以使用整合包中提供的mapper扫描器,此处的mappers不用配置了 --> 32 <mappers> 33 <!-- 通过resource引用mapper的映射文件 --> 34 <mapper resource="sqlmap/User.xml" /> 35 <!--<mapper resource="mapper/UserMapper.xml" />--> 36 <!-- 通过class引用mapper接口 class:配置mapper接口全限定名 要求:需要mapper.xml和mapper.java同名并且在一个目录中 --> 37 <!--<mapper class="com.emuii.mybatis.mapper.UserMapper" />--> 38 <!-- 重复加载映射 - Caused by: org.apache.ibatis.binding.BindingException: Type interface com.emuii.mybatis.mapper.UserMapper is already known to the MapperRegistry.--> 39 <!-- 批量mapper配置 --> 40 <package name="com.emuii.mybatis.mapper" /> 41 </mappers> 42 43 </configuration>
UserMapper.xml:将Sql语句配置在XXXXMapper.xml文件中与java代码分离,解决问题2
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语句进行隔离,方便管理 ,mapper开发dao方式,使用namespace有特殊作用 6 mapper代理开发时将namespace指定为mapper接口的全限定名 7 --> 8 <mapper namespace="com.emuii.mybatis.mapper.UserMapper"> 9 <!-- 在mapper.xml文件中配置很多的sql语句,执行每个sql语句时,封装为MappedStatement对象 10 mapper.xml以statement为单位管理sql语句 11 --> 12 13 <!-- 根据id查询用户信息 --> 14 <!-- 15 id:唯一标识 一个statement 16 #{}:表示 一个占位符,如果#{}中传入简单类型的参数,#{}中的名称随意 17 parameterType:输入 参数的类型,通过#{}接收parameterType输入 的参数 18 resultType:输出结果 类型,不管返回是多条还是单条,指定单条记录映射的pojo类型 19 --> 20 <select id="findUserById" parameterType="int" resultType="user"> 21 SELECT * FROM USER WHERE id= #{id} 22 </select> 23 24 <!-- 根据用户名称查询用户信息,可能返回多条 25 ${}:表示sql的拼接,通过${}接收参数,将参数的内容不加任何修饰拼接在sql中。 26 不能防止sql注入 27 --> 28 <select id="findUserByUsername" parameterType="java.lang.String" resultType="User"> 29 select * from user where username like ‘%${value}%‘ 30 </select> 31 32 <!-- 添加新用户 33 parameterType:输入 参数的类型,User对象 包括 username,birthday,sex,address 34 #{}接收pojo数据,可以使用OGNL解析出pojo的属性值 35 #{username}表示从parameterType中获取pojo的属性值 36 selectKey:用于进行主键返回,定义了获取主键值的sql 37 order:设置selectKey中sql执行的顺序,相对于insert语句来说 38 keyProperty:将主键值设置到哪个属性 39 resultType:select LAST_INSERT_ID()的结果 类型 40 --> 41 <insert id="insertUser" parameterType="user"> 42 <selectKey keyProperty="id" order="AFTER" resultType="int"> 43 SELECT LAST_INSERT_ID() 44 </selectKey> 45 insert into user(username,sex,birthday,address) values(#{username},#{sex},#{birthday},#{address}) 46 </insert> 47 <!-- mysql的uuid生成主键 --> 48 <!-- <insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"> 49 <selectKey keyProperty="id" order="BEFORE" resultType="string"> 50 select uuid() 51 </selectKey> 52 53 INSERT INTO USER(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address}) 54 </insert> --> 55 56 <!-- oracle 57 在执行insert之前执行select 序列.nextval() from dual取出序列最大值,将值设置到user对象 的id属性 58 --> 59 <!-- <insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"> 60 <selectKey keyProperty="id" order="BEFORE" resultType="int"> 61 select 序列.nextval() from dual 62 </selectKey> 63 64 INSERT INTO USER(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address}) 65 </insert> --> 66 67 <!-- 用户删除 --> 68 <delete id="deleteUserById" parameterType="int"> 69 delete from user where id = #{id} 70 </delete> 71 72 <!-- 用户更新 --> 73 <update id="updateUser" parameterType="User"> 74 update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} 75 </update> 76 77 </mapper>
UserMapper.java(和UserMapper.xml对应,两个随意先写哪一个)
1 package com.emuii.mybatis.mapper; 2 3 import com.emuii.mybatis.pojo.User; 4 import com.emuii.mybatis.pojo.UserQueryVo; 5 6 import java.util.List; 7 11 public interface UserMapper { 12 13 //根据id查询用户信息 14 public User findUserById(int id) throws Exception; 15 //根据用户名称模糊查询用户列表 16 public List<User> findUserByUsername(String username) throws Exception; 17 //插入用户 18 public void insertUser(User user) throws Exception; 19 20 }
UserMapperTest.java
1 package com.emuii.mybatis.mapper; 2 3 import com.emuii.mybatis.pojo.User; 4 import org.apache.ibatis.io.Resources; 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 8 import org.junit.Before; 9 import org.junit.Test; 10 11 import java.io.IOException; 12 import java.io.InputStream; 13 import java.util.Date; 14 import java.util.List; 15 16 /** 17 * Create by Leslie on 2018\1\4 0004.<br> 18 */ 19 public class UserMapperTest { 20 21 // 会话工厂 22 private SqlSessionFactory sqlSessionFactory; 23 24 // 创建工厂 25 @Before 26 public void init() throws IOException { 27 // 配置文件 28 String resource = "SqlMapConfig.xml"; 29 // 配置文件到输入流 30 InputStream inputStream = Resources.getResourceAsStream(resource); 31 // 创建会话工厂 32 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 33 } 34 35 @Test 36 // 根据id查询用户 37 public void testFindUserById() throws Exception { 38 SqlSession sqlSession = sqlSessionFactory.openSession(); 39 // 创建代理对象 40 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 41 42 User user = userMapper.findUserById(1); 43 sqlSession.close(); 44 45 System.out.println(user); 46 } 47 48 @Test 49 // 根据名称模糊查询用户 50 public void testFindUserByUsername() throws Exception { 51 SqlSession sqlSession = sqlSessionFactory.openSession(); 52 // 创建代理对象 53 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 54 55 List<User> user = userMapper.findUserByUsername("小明"); 56 sqlSession.close(); 57 58 System.out.println(user); 59 } 60 61 @Test 62 // 增加用户 63 public void testInsertUser() throws Exception { 64 SqlSession sqlSession = sqlSessionFactory.openSession(); 65 // 创建代理对象 66 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 67 User user = new User(); 68 user.setUsername("小米"); 69 user.setSex("2"); 70 Date date = new Date(); 71 user.setBirthday(date); 72 user.setAddress("法师锕"); 73 74 userMapper.insertUser(user); 75 sqlSession.commit(); 76 sqlSession.close(); 77 78 System.out.println(user); 79 } 80 81 }
ok!
问题3解决方式:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型
1 <!-- 将用户查询条件定义为sql片段 2 建议对单表的查询条件单独抽取sql片段,提高复用性 3 注意:不要讲where标签放在sql片段 4 --> 5 <sql id="query_user_where"> 6 <!-- 如果userQueryVo中传入查询条件,再进行sql拼接 --> 7 <!-- test中userCustom.username表示从userQueryVo读取属性值--> 8 <if test="userCustomer != null"> 9 <if test="userCustomer != null"> 10 and username like ‘%${userCustomer.username}%‘ 11 </if> 12 <if test="userCustomer.sex!=null and userCustomer.sex!=‘‘ "> 13 and sex = #{userCustomer.sex} 14 </if> 15 <!-- 等等等等等等。。。 --> 16 </if> 17 18 <if test="ids!=null"> 19 <!-- 根据id集合查询用户信息 --> 20 <!-- 最终拼接的效果: 21 SELECT id ,username ,birthday FROM USER WHERE username LIKE ‘%小明%‘ AND id IN (16,22,25) 22 collection:集合的属性 23 open:开始循环拼接的串 24 close:结束循环拼接的串 25 item:每次循环取到的对象 26 separator:每两次循环中间拼接的串 27 --> 28 <foreach collection="ids" open="AND id IN (" close=")" item="id" separator=","> 29 #{id} 30 </foreach> 31 </if> 32 </sql>
使用:
1 <!-- 自定义查询条件查询用户的信息 2 parameterType:指定包装类型 3 %${userCustomer.username}% : userCustomer是userQueryVo中的属性,通过OGNL获取属性的值 4 --> 5 <select id="findUserList" parameterType="userQueryVo" resultType="user"> 6 SELECT id,username,birthday from user 7 <!-- 如果userQueryVo中传入查询条件,再进行sql拼接 --> 8 <where> 9 <!-- 引用sql片段,如果sql片段和引用处不在同一个mapper必须前边加namespace --> 10 <include refid="query_user_where" /> 11 <!-- 下边还有很其它的条件 --> 12 <!-- <include refid="其它的sql片段"></include> --> 13 </where> 14 </select> 15 16 <!-- 查询用户记录 --> 17 <select id="findUserCount" parameterType="userQueryVo" resultType="int"> 18 SELECT count(*) from user 19 <where> 20 <include refid="query_user_where" /> 21 </where> 22 </select>
UserMapper.java
UserQueryVo.java
Test
1 @Test 2 public void testFindUserList() throws Exception { 3 SqlSession sqlSession = sqlSessionFactory.openSession(); 4 // 创建代理对象 5 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 6 7 // 构造查询条件 8 UserQueryVo userQueryVo = new UserQueryVo(); 9 10 UserCustomer userCustomer = new UserCustomer(); 11 userCustomer.setUsername("小明"); 12 userCustomer.setSex("1"); 13 14 userQueryVo.setUserCustomer(userCustomer); 15 16 // id集合 17 List<Integer> ids = new ArrayList<Integer>(); 18 ids.add(16); 19 ids.add(22); 20 userQueryVo.setIds(ids); 21 22 List<User> list = userMapper.findUserList(userQueryVo); 23 // User list = userMapper.findUserList(userQueryVo); 24 25 sqlSession.close(); 26 27 System.out.println(list); 28 } 29 30 @Test 31 public void testFindUserCount() throws Exception { 32 SqlSession sqlSession = sqlSessionFactory.openSession(); 33 // 创建代理对象 34 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 35 36 // 构造查询条件 37 UserQueryVo userQueryVo = new UserQueryVo(); 38 39 UserCustomer userCustomer = new UserCustomer(); 40 userCustomer.setUsername("小明"); 41 42 userQueryVo.setUserCustomer(userCustomer); 43 44 Integer count = userMapper.findUserCount(userQueryVo); 45 // User list = userMapper.findUserList(userQueryVo); 46 47 sqlSession.close(); 48 49 System.out.println(count); 50 }
问题四:对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
另外:
mybatis与hibernate重要区别
企业开发进行技术选型 ,考虑mybatis与hibernate适用场景。
mybatis:入门简单,程序容易上手开发,节省开发成本 。mybatis需要程序员自己编写sql语句,是一个不完全 的ORM框架,对sql修改和优化非常容易实现 。
mybatis适合开发需求变更频繁的系统,比如:互联网项目。
hibernate:入门门槛高,如果用hibernate写出高性能的程序不容易实现。hibernate不用写sql语句,是一个 ORM框架。
hibernate适合需求固定,对象数据模型稳定,中小型项目,比如:企业OA系统。
总之,企业在技术选型时根据项目实际情况,以降低成本和提高系统 可维护性为出发点进行技术选型。