Mybatis
Jdbc演化到mybatis
1.package cn.test.jdbc.test; 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.public class JDBCTest { 10. 11. public static void main(String[] args) { 12. Connection connection = null; 13. PreparedStatement preparedStatement = null; 14. ResultSet resultSet = null; 15. try { 16. //加载数据库驱动 17. Class.forName("com.mysql.jdbc.Driver"); 18. 19. //通过驱动管理类获取数据库链接 20. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis01?characterEncoding=utf-8", "root", "admin"); 21. //定义sql语句 ?表示占位符 22. String sql = "select * from user where username = ?"; 23. //获取预处理statement 24. preparedStatement = connection.prepareStatement(sql); 25. //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 26. preparedStatement.setString(1, "王五"); 27. //向数据库发出sql执行查询,查询出结果集 28. resultSet = preparedStatement.executeQuery(); 29. //遍历查询结果集 30. while(resultSet.next()){ 31. System.out.println(resultSet.getString("id")+" "+resultSet.getString("username")); 32. } 33. } catch (Exception e) { 34. e.printStackTrace(); 35. }finally{ 36. //释放资源 37. if(resultSet!=null){ 38. try { 39. resultSet.close(); 40. } catch (SQLException e) { 41. // TODO Auto-generated catch block 42. e.printStackTrace(); 43. } 44. } 45. if(preparedStatement!=null){ 46. try { 47. preparedStatement.close(); 48. } catch (SQLException e) { 49. // TODO Auto-generated catch block 50. e.printStackTrace(); 51. } 52. } 53. if(connection!=null){ 54. try { 55. connection.close(); 56. } catch (SQLException e) { 57. // TODO Auto-generated catch block 58. e.printStackTrace(); 59. } 60. } 61. 62. } 63. 64. } 65. 66. 67.}
Mybatis优化
第一步优化:连接获取和释放
问题描述:
数据库连接频繁的开启和关闭本身就造成了资源的浪费,影响系统的性能。
解决问题:
数据库连接的获取和关闭我们可以使用数据库连接池来解决资源浪费的问题。通过连接池就可以反复利用已经建立的连接去访问数据库了。减少连接的开启和关闭的时间。
问题描述:
但是现在连接池多种多样,可能存在变化,有可能采用DBCP的连接池,也有可能采用容器本身的JNDI数据库连接池。
解决问题:
我们可以通过DataSource进行隔离解耦,我们统一从DataSource里面获取数据库连接,DataSource具体由DBCP实现还是由容器的JNDI实现都可以,所以我们将DataSource的具体实现通过让用户配置来应对变化。
第二步优化:SQL统一存取
问题描述:
我们使用JDBC进行操作数据库时,SQL语句基本都散落在各个JAVA类中,这样有三个不足之处:
第一,可读性很差,不利于维护以及做性能调优。
第二,改动Java代码需要重新编译、打包部署。
第三,不利于取出SQL在数据库客户端执行(取出后还得删掉中间的Java代码,编写好的SQL语句写好后还得通过+号在Java进行拼凑)。
解决问题:
我们可以考虑不把SQL语句写到Java代码中,那么把SQL语句放到哪里呢?首先需要有一个统一存放的地方,我们可以将这些SQL语句统一集中放到配置文件或者数据库里面(以key-value的格式存放)。然后通过SQL语句的key值去获取对应的SQL语句。
既然我们将SQL语句都统一放在配置文件或者数据库中,那么这里就涉及一个SQL语句的加载问题。
第三步优化:传入参数映射和动态SQL
问题描述:
很多情况下,我们都可以通过在SQL语句中设置占位符来达到使用传入参数的目的,这种方式本身就有一定局限性,它是按照一定顺序传入参数的,要与占位符一一匹配。但是,如果我们传入的参数是不确定的(比如列表查询,根据用户填写的查询条件不同,传入查询的参数也是不同的,有时是一个参数、有时可能是三个参数),那么我们就得在后台代码中自己根据请求的传入参数去拼凑相应的SQL语句,这样的话还是避免不了在Java代码里面写SQL语句的命运。既然我们已经把SQL语句统一存放在配置文件或者数据库中了,怎么做到能够根据前台传入参数的不同,动态生成对应的SQL语句呢?
第四步优化:结果映射和结果缓存
问题描述:
执行SQL语句、获取执行结果、对执行结果进行转换处理、释放相关资源是一整套下来的。假如是执行查询语句,那么执行SQL语句后,返回的是一个ResultSet结果集,这个时候我们就需要将ResultSet对象的数据取出来,不然等到释放资源时就取不到这些结果信息了。我们从前面的优化来看,以及将获取连接、设置传入参数、执行SQL语句、释放资源这些都封装起来了,只剩下结果处理这块还没有进行封装,如果能封装起来,每个数据库操作都不用自己写那么一大堆Java代码,直接调用一个封装的方法就可以搞定了。
解决问题:
我们分析一下,一般对执行结果的有哪些处理,有可能将结果不做任何处理就直接返回,也有可能将结果转换成一个JavaBean对象返回、一个Map返回、一个List返回等等,结果处理可能是多种多样的。从这里看,我们必须告诉SQL处理器两点:第一,需要返回什么类型的对象;第二,需要返回的对象的数据结构怎么跟执行的结果映射,这样才能将具体的值copy到对应的数据结构上。
接下来,我们可以进而考虑对SQL执行结果的缓存来提升性能。缓存数据都是key-value的格式,那么这个key怎么来呢?怎么保证唯一呢?即使同一条SQL语句几次访问的过程中由于传入参数的不同,得到的执行SQL语句也是不同的。那么缓存起来的时候是多对。但是SQL语句和传入参数两部分合起来可以作为数据缓存的key值。
第五步优化:解决重复SQL语句问题
问题描述:
由于我们将所有SQL语句都放到配置文件中,这个时候会遇到一个SQL重复的问题,几个功能的SQL语句其实都差不多,有些可能是SELECT后面那段不同、有些可能是WHERE语句不同。有时候表结构改了,那么我们就需要改多个地方,不利于维护。
解决问题:
当我们的代码程序出现重复代码时怎么办?将重复的代码抽离出来成为独立的一个类,然后在各个需要使用的地方进行引用。对于SQL重复的问题,我们也可以采用这种方式,通过将SQL片段模块化,将重复的SQL片段独立成一个SQL块,然后在各个SQL语句引用重复的SQL块,这样需要修改时只需要修改一处即可。
优化总结:
我们总结一下上面对JDBC的优化和封装:
(1) 使用数据库连接池对连接进行管理
(2) SQL语句统一存放到配置文件
(3) SQL语句变量和传入参数的映射以及动态SQL
(4) 动态SQL语句的处理
(5) 对数据库操作结果的映射和结果缓存
mybatis简介
Mybatis是什么?
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回
Mybatis是apache旗下的顶级一个项目,mybatis是对jdbc的封装的一个持久层框架
架构设计图
对比hibernate.cfg.xml:hibernate基础环境配置文件—连接关联(数据源),事务管理。
基础支撑层:
Mybatis:sqlMapConfig.xml(全局配置文件)
1) 数据源
2) 事务
3)加载映射文件
数据处理层:
1) Mybatis接受参数进行增加,删除,修改,查询,进行结构集映射。
接口层:
1)程序员可以调用接口一些方法,实现增加,删除,修改,查询(SqlSession)
mybatis框架运行流程
l mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
l 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
l 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
l mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
l Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
l Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
mybatis入门程序
需求
根据用户ID(主键)进行查询用户信息
根据用户名称模糊查询用户信息
添加用户
删除用户
更新用户
环境
Java环境:jdk1.7.0_72
Eclipse:kepler
Mysql:5.5
mybaits的代码由github.com管理,地址:https://github.com/mybatis/mybatis-3/releases
mybatis-3.2.7.jar----mybatis的核心包
lib----mybatis的依赖包
mybatis-3.2.7.pdf----mybatis使用手册
mybatis入门程序
环境准备
创建一个Java工程
导入jar文件
log4j日志
创建sqlMapConfig.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> <!-- 初始化环境,和spring整合以后,这个将会被废除 --> <environments default="development"> <environment id="development"> <!-- 使用jdbc管理事务,事务控制是由mybatis来执行 --> <transactionManager type="JDBC"/> <!-- 数据源 也是由mybatis来进行管理--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="admin"/> </dataSource> </environment> </environments> <mappers> <!-- 引入映射文件 --> <!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/> --> </mappers> </configuration>
sqlMapConfig是mybatis的核心配置文件,包含数据源,事务管理等等
创建PO类
1.1.1.1 准备Mapper映射文件
在classpath下sqlMap里面创建映射文件User.xml(此名称可以任意定义)
测试开发:定义映射文件user.xml
User.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"> <!-- namespace是命名空间,用来隔离sql语句,可以随意定义, 注意:在使用代理时具有特殊的约定含义,不能随意定义 --> <mapper namespace="test"> </mapper>
Namespace:命名空间 用来隔离sql语句,在mybatis代理模式具有特殊的含义
详细的mapper文件如下:
<?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"> <!-- namespace是命名空间,用来隔离sql语句,可以随意定义, 注意:在使用代理时具有特殊的约定含义,不能随意定义 --> <mapper namespace="test"> <!-- 首先明确:mybatis的所有sql语句都存放在xml的映射文件中,也就是说有多个sql --> <!-- id:id就是我们唯一标识一个sql语句的地址 mybatis会将sql语句封装到mappedStatement对象中,id就是statement的id 注意:我们需要根据ID进行查询,需要接受参数使用parameterType进行接受参数 #,$都用来接受参数,就是占位符接受参数,注意:如果传递的是基本类型的参数,#{??}里面可以任意 parameterType:传递参数 resultType:结果集映射 --> <select id="selectUserByID" parameterType="int" resultType="cn.test.bean.User"> select * from user where id=#{id} </select> </mapper>
ID查询测试类
测试代码:
public class MybatisTest { private SqlSessionFactory sessionFactory; @Before public void createSessionFactory() throws Exception{ String res="sqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(res); sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void test02(){ //获取Session SqlSession session = sessionFactory.openSession(); User user = session.selectOne("test.selectUserByID", 1); System.out.println(user); session.close(); } @Test public void test01() throws Exception{ String resources="sqlMapConfig.xml"; //读取文件资源流 InputStream inputStream = Resources.getResourceAsStream(resources); //创建一个SessionFactory SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取Session SqlSession session = sessionFactory.openSession(); User user = session.selectOne("test.selectUserByID", 1); System.out.println(user); session.close(); } }
根据姓名username模糊查询
映射文件:
<!--需求:根据用户姓名进行模糊查询,可能返回多条记录 resultType:指定返回单条记录,映射成Java单个对象 ${}拼接sql语句,对参数不加任何修饰的拼接在sql语句中 ${value}:如果传入的参数类型是基本类型,那么${}内只能是value --> <select id="selectUserByUsername" parameterType="string" resultType="cn.itcast.bean.User"> select * from user where username like '%${value}%' </select>
测试代码模糊查询:
@Test public void selectUserByUsername(){ SqlSession session = sessionFactory.openSession(); //第一个参数:statement的id,根据id找到需要执行的sql //第二个参数:指定匹配parameterType的参数类型 List<User> list = session.selectList("test.selectUserByUsername", "张"); System.out.println(list); }
#,$
#{}表示一个占位符,通过#{}可以实现preparedStatement向占位符中设置值,自动进行Java
类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接受简单类型值或pojo属性值。
如果parameterType传输单个类型的值,#{}括号中可以是value或者其他任意值。
${}表示拼接sql串,通过${}可以通过patameterType传入的内容拼接在sql中且不进行jdbc类型转换。${}可以接受简单类型值或pojo属性值,如果parameterType传输单个类型值,${}括号中只能是value.
parameterType和resultType
patameterType:指定传递参数类型,mybatis通过ognl获取参数拼接在sql语句中
resultType:指定返回结果类型,mybatis可以把查询结果封装成Java对象,这里就是把结果集映射成resultType指定的对象。
selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查询一条或多条记录。
添加
映射文件(user.xml)
<!-- parameterType:指定输入参数类型pojo,就是javabean对象 #{}口号中是Javabean的属性名,mybatis通过ognl来获取javabean的属性值 --> <insert id="insertUser" parameterType="cn.itcast.bean.User"> <!--使用sql函数生成主键ID 注意:mysql主键是在执行完成insert语句后才增加主键,所以使用after --> <selectKey keyProperty="id" order="AFTER" resultType="int"> SELECT LAST_INSERT_ID() </selectKey> insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
Mysql自增主键返回:
selectKey实现自增主键返回:
keyProperty:返回值对应pojo里面的那个属性。
Order:表示id生成的顺序,由于mysql主键生成是在sql语句执行之后在进行设置,所以我们设置成after。
ResultType:主键返回类型
LAST_INSERT_ID()是mysql函数,返回auto_increament自增id值
也可以使用useGeneratedKeys来返回主键
添加测试代码:
@Test public void test01() throws Exception{ SqlSession sqlSession = sessionFactory.openSession(); User user = new User(); user.setUsername("张三"); user.setSex("男"); user.setBirthday(new Date()); user.setAddress("陕西"); sqlSession.insert("test.insertUser", user); System.out.print(user.getId);//使用last_insert_Id返回ID sqlSession.commit(); sqlSession.close(); }
删除
映射文件(user.xml)
测试代码:
@Test public void deleteUserByID(){ SqlSession session = sessionFactory.openSession(); //删除单表,没有外键关联才能被删除 session.delete("test.deleteUserByID", 32); session.commit(); session.close(); }