MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。 ●【GitHub】 GitHub就是一个互联网上的超大SVN库,里面维护着许多开源项目的代码。任何人都可以把自己好多项目代码放上去共享,同时接受其他人的共同开发。 2.2.什么是MyBatis MyBatis是使用java语言编写的一个优秀的持久层框架,是对JDBC操作数据库的过程进行了全新的封装。解决了JDBC中的问题。 Mybatis框架隐藏了jdbc繁杂的业务无关代码: ·手动注册驱动、创建connection、statement ·手动设置参数以及参数的顺序 ·手动遍历结果集 使开发者只需关注SQL怎么写。 3.JDBC的问题 JDBC是原生的数据库开发。JDBC对于单机版的软件或者一个小办公室的小系统都还是可以应付的,毕竟业务简单,数据量小,程序规模都不大。修改、编译、发布都很容易。但随着业务发展这样的IT技术不能满足要求了。逐渐的暴露出一些问题。 public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); //通过驱动管理类获取数据库链接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root"); //定义sql语句 ?表示占位符 String sql = "select * from user where username = ? and age = ?"; //获取预处理statement preparedStatement = connection.prepareStatement(sql); //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五"); preparedStatement.setInt(2, 18); //向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); List<User> userList = new ArrayList<User>(); //遍历查询结果集 while(resultSet.next()){ User user = new User(); user.setUserId(resultSet.getInt("id")); user.setName(resultSet.getString("username")); userList.add(user); } } catch (Exception e) { e.printStackTrace(); }finally{ //释放资源 if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(preparedStatement!=null){ try { preparedStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 从上面的问题总结如下: 1.数据库连接的创建、释放频繁造成系统资源浪费,缺乏有效管理,非常影响系统性能。 如果使用数据库连接池可解决此问题。 2.程序中存在硬编码:(硬编码就是写死在程序中的固定值) 1)数据库连接字符串:换数据库就要改代码,就要重新编译发布,维护压力增大。 2)SQL定义字符串:SQL修改的可能性较大,修改SQL就要重新编译发布,维护压力增大。 3)传参数时的参数位置:参数必须按照先后顺序设置到对应的SQL条件上,十分不灵活。 4)结果集中字段名字符串:字段名变化就要改代码,就要重新编译发布,维护压力增大。 3.1.如何解决JDBC的问题 框架发明的目的之一就是为了解决jdbc问题,比如:Hibernate、MyBatis等。这些框架不仅可以解决问题还可以大大简化开发,让开发人员更好的集中精力实现业务逻辑。 4.MyBatis主要的内容 MyBatis最重要的就是写好SQL,包括写好SQL语句和写好SQL映射。 ·SQL语句就是标准的SQL语句(可以在当前选用的数据库产品下,根据需求使用该产品下的SQL函数) ·SQL映射包括:参数映射和返回值映射(返回值只针对查询,增删改是没有返回值的) ●【参数映射】(也叫做【输入映射】) MyBatis将java对象传入给SQL语句参数的过程。 ●【返回值映射】(也叫做【输出映射】) MyBatis将SQL查询的结果集处理成一个java对象并返回给java程序的过程。 ●【java对象】 如果是单个参数映射,可以是java简单类型变量:int、long、float、String、Integer、Long、Boolean、Float等。参数值可以映射给sql。 如果是多个参数映射,必须是用java bean,有一个名词叫pojo(Plain Ordinary Java Object),里面有许多属性以及它们的getter/setter方法。将多个参数封装到pojo对象里面,一起映射给sql。Java bean和pojo没有区别,就是换种叫法而已。 ● SQL语句以及映射写在xml或注解中。 5.MyBatis访问数据库的核心构成 我们通过Hibernate与MyBatis访问数据库核心构成的对比来学习MyBatis如何访问数据库的。 从这个结构图要明确知道MyBatis访问数据库的核心构成包括三个核心类和两种配置文件。 三个核心类:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession; 两种配置文件:MyBatis核心配置文件(一个)、MyBatis映射文件(多个)。 我们在下面学习的入门程序主要学习的就是参数映射的规范和返回值映射的规范。 6.MyBatis开发环境搭建 这个开发环境仅仅是学习使用的,并不是真正项目中使用的开发环境。 6.1.第一步:创建数据库和表 1.创建数据库【mybatis】,编码utf-8 2.导入【资料\01.数据库\initialDB.sql】脚本。 6.2.第二步:创建工程 创建一个普通java工程。 因为这里我们只是用来学习MyBatis,所以不是实际项目中真正的开发环境,因此建立一个普通java工程就够用了。 等都后面SSM整合之后才是我们真正在实际项目中使用的开发环境。 6.3.第三步:导入jar包 这里我们用到【MyBatis的jar包】和【mysql的jar包】。 1.取得MyBatis的jar包 Mybatis的GitHub地址: https://github.com/mybatis/mybatis-3/releases 2.导入MyBatis的jar包 1)Mybatis的jar包分为: 核心jar包 依赖jar包 2)导入工程并关联到工程 3. mysql的jar包也要导入工程并关联,方法同上。 6.4.第四步:框架的配置文件 6.4.1.核心配置文件 创建Source Folder【config】,它作为全部关于环境的配置文件的存放目录。 在config下创建MyBatisConfig.xml(也有人叫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> <!-- 数据库环境的配置(临时配置) --> <!-- environments:表示多个数据库环境的配置标签 default:当前默认使用的数据库环境 --> <environments default="dev"> <!-- 开发数据库环境的配置 --> <!-- environment:表示一个数据库环境配置的标签 id:表示唯一标识一个数据库环境 --> <environment id="dev"> <!-- 事务管理的配置 --> <!-- transactionManager:表示事务管理的配置标签 type:表示事务管理的类型,由于MyBatis是对JDBC的封装,所以通常使用JDBC的事务 --> <transactionManager type="JDBC"/> <!-- 数据源配置:driver, url, username, password --> <!-- dataSource:表示数据源的配置标签 type:表示数据源的类型,标识是否支持数据库连接池 POOLED:表示支持数据库连接池的数据源 UNPOOLED:表示不支持数据库连接池的数据源 --> <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="123"/> </dataSource> </environment> </environments> </configuration> 这个文件就是MyBatis的核心配置文件,里面主要配置连接数据库的数据源、事务管理以及MyBatis的映射文件有哪些。 其中整个【environments】部分不需要掌握,大概知道里面配置的是数据库信息就可以了,因为到实际项目中框架整合后就不用这么配置了,所以它没有实际用途,这里只是临时这么用一下。 6.4.2.SQL映射文件 MyBatis的SQL映射文件就是用来写SQL语句和配置SQL的参数映射和返回值映射的,可以根据业务创建多个映射文件,比如关于用户信息的映射文件:UserMapper.xml,关于订单信息的映射文件:OrderMapper.xml等。 1.创建SQL映射文件(因为SQL映射文件是关于业务的,所以不要放到config里面) 创建一个包【cn.baidu.mapper】,在其下创建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"> <!-- namespace:隔离SQL映射文件的,是一个SQL映射文件的唯一标识 --> <mapper namespace="user"> <!-- SQL映射 --> </mapper> 2.配置SQL映射文件 在MyBatis核心配置文件中配置映射文件,目的是为了让MyBatis知道有这个映射文件。 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> 。。。。。。 <!-- 配置映射文件 --> <mappers> <!-- 通过映射文件在编译后类目录下的相对路径加载映射文件 resource:用来指定映射文件的相对路径 --> <mapper resource="cn/baidu/mapper/UserMapper.xml" /> </mappers> </configuration> 6.5.第六步:测试开发环境 1.利用junit进行测试,eclipse本身包含junit的jar直接导入即可: 2.手动编写客户端测试程序 创建Source Folder【test】用于存放测试程序的,并创建一个普通的class: package mybatis; 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 org.junit.Test; public class MyTest { @Test public void envTest() throws Exception { SqlSession sqlSession = null; try { // 1. 读取配置文件(MyBatis有专门读取配置文件的工具类Resources) InputStream inputStream = Resources.getResourceAsStream("MyBatisCofig.xml"); // 2. 根据主配置文件创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3. 根据会话工厂创建会话对象 // 业务层通过SqlSession对象来访问数据库进行CRUD操作,每个执行方法中会话对象要私有 sqlSession = sqlSessionFactory.openSession(); System.out.println(sqlSession); } catch(Exception e) { e.printStackTrace(); throw e; } finally { // 关闭会话 sqlSession.close(); } } } 注意:上面的核心类不是真实项目开发中需要写的,都是临时的写法。这里由于只使用了MyBatis框架,所以只能临时手动的加载核心配置文件、手动创建会话工厂以及会话对象,到真实项目中这些都不用写。这里只是做测试用的代码。大家只要知道三个核心类是什么就可以。 3.能够打印出SqlSession对象的信息说明客户端与数据库连接的会话已经建立起来了。 6.6.第七步:给MyBatis加日志输出 把【资料\04.参考案例\config\log4j.properties】拷贝到工程的config目录下。 6.7.小结 这一节主要是操作,因此记忆的东西很少,主要是课后多加练习开发环境的搭建。 搭建开发环境的目录步骤可以看目录。 7.MyBatis入门程序(重点) MyBatis程序开发分为两步: 1. 在SQL映射文件中编写SQL以及SQL映射,包括参数映射和返回值映射。 2. 在客户端用SqlSession对象使用指定的方法调用SQL,包括两个参数:第一个参数是某个配置好的SQL映射,第二个参数是要传给SQL的参数。 7.1.查 7.1.1.根据id查询用户信息 1.【UserMapper.xml】映射文件中增加查询SQL的映射配置: <说明>(需要掌握) 项目 解释 <select> 用于查询SQL的标签。 id 在相同映射文件中SQL的唯一标识(名称不允许包含点【.】) parameterType 传入参数的类型(当没有参数时可以省略) resultType SQL返回给程序的java结果的类型 #{userId} 以#{xxx}作为样式,叫做占位符。用来接收参数。xxx表示参数的变量名称。 MyBatis都是按名称进行参数映射的,如果只写#{}会报错。有了名称就不用考虑jdbc参数赋值的先后顺序了,所以解决了jdbc传值顺序的硬编码问题。 <SQL示例> 注意:要严格按照MyBatis的要求和映射规范去写xml,否则MyBatis就无法解析了。 <?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="user"> <!-- SQL --> <!-- 根据id查询用户信息 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> </mapper> <SQL映射规范> ·参数映射规范(一) 传单个参数时,parameterType="java简单类型",占位符中的变量可以任意名称,但不能没有。 ·返回值映射规范(一) 返回单条记录时,resultType="pojo类型",结果集的列名必须等于pojo的属性名。 (注意单条记录中的多个值不能分散的返回,MyBatis不支持) 2.【MyTest.java】中增加一个测试方法: <说明>(需要掌握) 项目 解释 selectOne 查询单个记录(单值或单条都使用它) 第一个参数 namespace属性的值 + . + SQL id属性的值(namespace确定映射文件,id确定SQL) 第二个参数 传给SQL的参数,类型 = parameterType指定的类型(当没有参数时可省略) 返回值 SQL查询的结果,类型 = resultType指定的类型 <代码示例> // 测试根据id查询用户信息 @Test public void test1() throws Exception { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根据配置文件创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根据会话工厂创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 根据id查询用户信息 User userInfo = sqlSession.selectOne("user.findUserById", 1001); System.out.println(userInfo); sqlSession.close(); } 7.2.根据用户名查询用户信息 因为用户名是一个不确定的查询条件,因此多半在SQL采用模糊查询的方式进行条件匹配。 7.2.1.用占位符接收参数映射 1.【UserMapper.xml】映射文件中增加查询SQL配置 占位符有一个特性:可以做参数类型的判断,如果是字符串类型的参数会自动添加单引号,不需要手动添加。 <!-- 根据用户名查询用户信息(方式一:用占位符接收参数映射) --> <select id="findUserByUserName" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE #{userName} </select> <SQL映射规范> ·返回值映射规范(二) 返回多条记录时,resultType="集合的pojo泛型的类型",结果集的列名必须等于pojo泛型的属性名。 2.【MyTest.java】中增加一个测试方法: <说明>(需要掌握) 项目 解释 selectList 查询多条记录(返回List<pojo>类型的java对象) 第一个参数 同上 第二个参数 同上 返回值 SQL查询的List集合结果,List集合的泛型 = resultType指定的类型 MyBatis内部会通过返回值映射产生多个java对象,这些对象会放到一个List对象中,每个对象的类型就是resultType配置的泛型,最终将List对象返回给java程序。 <代码示例> // 测试根据用户名查询用户信息(方式一) @Test public void test1() throws Exception { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根据配置文件创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根据会话工厂创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //根据用户名查询用户信息 List<User> userList = sqlSession.selectList("user.findUserByUserName", "%王%"); System.out.println(userList); sqlSession.close(); } 注意:这里有一个问题就是如果SQL不是你写的,你在调用的时候可能不知道里面是否用的是模糊查询,所以也就不好判断是否需要加百分号了,最好是能将百分号加在SQL上面,这样外面不管是不是模糊查询都需要传姓名肯定是没有错的。但这时就不能使用占位符的,因为单引号需要自己加在SQL中。 这就需要使用MyBatis的另一种用来接收参数映射的符号——字符串连接符,也叫字符串拼接符。 7.2.2.用字符串拼接符接收参数映射 1.【UserMapper.xml】映射文件中增加查询SQL配置 <说明> 项目 解释 ${value} 以${xxx}作为样式,叫做字符串拼接符。 拼接符让MyBatis把SQL语句和参数值当成字符串进行字符串原样拼接,所谓原样拼接就是不做任何jdbc类型转换,原来什么样就拼成什么样。所以SQL配置中必须人为加单引号才行。 <SQL示例> <!-- 根据用户名查询用户信息(方式二:用拼接符接收参数) --> <select id="findUserByUserName2" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> <SQL映射规范> ·参数映射规范(二) 传单个参数时,parameterType="java简单类型",拼接符中的变量名必须是value,也不能没有。 2.【MyTest.java】中增加一个测试方法: // 测试根据用户名查询用户信息(方式二) @Test public void test1() throws Exception { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根据配置文件创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 根据会话工厂创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //根据用户名查询用户信息 List<User> userList = sqlSession.selectList("user.findUserByUserName2", "王"); System.out.println(userList); sqlSession.close(); } 7.2.3.占位符与拼接符区别 1.类型处理: 占位符#{}传递参数时会做参数类型处理, 拼接符${}传递参数时不会做类型处理只进行字符串原样拼接 2.安全性: ${}的原样拼接导致它存在安全漏洞,容易产生SQL注入风险 #{}的类型处理会对参数中存在的SQL敏感字符先转义然后再映射给SQL,这就不会影响原先的SQL,因此可以有效防止SQL注入。 3.工作中的应用: 由于拼接符${}存在安全隐患,因此在实际项目尽量使用占位符#{} 附:SQL注入的一个示例 1 映射文件中的配置 <!-- 用拼接符接收参数 --> <select id="selectUserByUserName3" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE '${value}' </select> <!-- 用占位符接收参数 --> <select id="selectUserByUserName4" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE #{name} </select> 2 传递参数是一致的,左边拼接符最外面的单引号已经在映射文件中写上了;右边占位符按照预想由于传入的是String字符串类型的参数,所以会做类型处理自动的在参数外面加上一对单引号。但事情会想我们想象的那样进行吗? List<User> userInfoList = sqlSession.selectList("user. selectUserByUserName3", "' OR 1=1 OR 1='"); List<User> userInfoList = sqlSession.selectList("user. selectUserByUserName4", "' OR 1=1 OR 1='"); 3 结果发现左边确实发生了sql注入,右边没有发生: DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - ==> Preparing: SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE '' OR 1=1 OR 1='' DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 14 [[1001, 王小一, null, 56, null, 南京], [1002, 王小二, null, 48, null, 青岛], [1003, 王小三, null, 32, null, 大连], [1004, 张三, null, 23, null, 广州], [1005, 王小五, null, 34, null, 重庆], [1006, 王小六, null, 31, null, 石家庄], [1007, 迎春, null, 28, null, 苏州], [1008, 张三, null, 23, null, 广州], [1009, 迎秋, null, 20, null, 长沙], [1010, 迎冬, null, 18, null, 佛山], [1011, 张三, null, 30, null, 广州], [1013, 张三, null, 30, null, 广州], [1014, 张三, null, 30, null, 广州], [1015, 张三, null, 30, null, 广州]] DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee] DEBUG [main] - Returned connection 1177377518 to pool. DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - ==> Preparing: SELECT u.userId, u.name, u.age, u.address FROM user u WHERE u.name LIKE ? DEBUG [main] - ==> Parameters: ' OR 1=1 OR 1='(String) DEBUG [main] - <== Total: 0 [] DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@58c1670b] DEBUG [main] - Returned connection 1489069835 to pool. 左边拼接是原样拼接因此出现了漏洞,形成的SQL相当于mysql的工具中左边的样子: 右边占位符由于做类型处理,首先后把校验传入的参数是否有敏感字符,这里单引号就是一个敏感字符,其次如果有敏感字符需要进行转义,上面的参数转义为:\' OR 1=1 OR 1=\',再次再把转义完的参数映射给SQL并在参数外面加一对单引号,转义后的参数就不会对原先的SQL产生影响,仅仅被当作普通参数值进行处理。形成的SQL相当于mysql的工具中右边的样子: 7.3.查询用户表记录数 1.【UserMapper.xml】映射文件中增加查询SQL配置 <说明> 项目 解释 <select> 同上 id 同上 parameterType 没有参数时可以省略 resultType SQL返回给程序的java结果的类型 #{xxx}或${xxx} 没有参数可以省略 <SQL示例> <!-- 取得用户表的记录数 --> <select id="countUserRecord" resultType="int"> SELECT COUNT(userId) FROM user </select> 注意:不要使用count(*),因为count(*)效率低,可以count(1)或count(字段名)都可以。 <SQL映射规范> ·返回值映射规范(三) 返回单值时,resultType="java简单类型",值直接返回给java程序。 2.【MyTest.java】中增加一个测试方法: // selectOne也可以返回单值结果 int count = ss.selectOne("user.countUserRecord"); System.out.println(count); 7.4.增 7.4.1.插入单条记录 1.【UserMapper.xml】映射文件中增加插入SQL配置 <说明> 项目 解释 <insert> 用于插入SQL的标签。 id 同查询 parameterType 同查询 resultType 插入SQL没有返回值,所以没有这个属性 #{xxx} 同查询,这里能体现出名称的价值。 <SQL示例> <!-- 插入用户信息 --> <insert id="addUserInfo" parameterType="cn.baidu.pojo.User"> INSERT INTO USER (name,mobile,sex,age,address) VALUES (#{name},#{mobile},#{sex},#{age},#{address}) </insert> <SQL映射规范> ·参数映射规范(三) 传多个参数时,parameterType="pojo类型",占位符或拼接符的变量名必须等于pojo中的属性名。 (在参数pojo中属性是没有顺序的,所以很好的解决了jdbc参数顺序硬编码的问题) 2.【MyTest.java】中增加一个测试方法: <说明> 项目 解释 insert 插入处理 第一个参数 同查询 第二个参数 同查询 返回值 SQL没有返回值,但是方法本身有一个int的返回值,表示插入的记录条数。 <代码示例> // 测试插入一条用户信息 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根据会话工厂创建会话对象 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAge(18); user.setAddress("北京"); user.setMobile("13500099000"); user.setName("张三"); user.setSex("男"); // 插入用户信息 int count = sqlSession.insert("user.addUserInfo", user); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.5.改 7.5.1.根据id更新用户信息 1.【UserMapper.xml】映射文件中增加插入SQL配置 <说明> 项目 解释 <update> 用于更新SQL的标签。 id 同查询 parameterType 同查询 resultType 更新SQL没有返回值,所以没有这个属性 #{xxx} 同查询,这里能体现出名称的价值。 <SQL示例> <!-- 根据id修改用户信息 --> <update id="updateUserById" parameterType="cn.baidu.pojo.User"> UPDATE user SET name = #{name}, mobile = #{mobile}, sex = #{sex}, age = #{age}, address = #{address} WHERE userId = #{userId} </update> <SQL映射规范> 同插入的规范。 2.【MyTest.java】中增加一个测试方法: <说明> 项目 解释 update 更新处理 第一个参数 同查询 第二个参数 同查询 返回值 SQL没有返回值,但是方法本身有一个int的返回值,表示更新的记录条数。 <代码示例> // 测试根据id修改用户信息 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根据会话工厂创建会话对象 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAddress("天津"); user.setAge(28); user.setMobile("13600099000"); user.setName("李四"); user.setSex("女"); user.setUserId(1011); // 更新用户信息 int count = sqlSession.update("user.updateUserById", user); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.6.删 7.6.1.根据id删除用户信息 1.【UserMapper.xml】映射文件中增加插入SQL配置 <说明> 项目 解释 <delete> 用于删除SQL的标签。 id 同查询 parameterType 同查询 resultType 删除SQL没有返回值,所以没有这个属性 #{xxx} 同查询 <SQL示例> <!-- 根据id删除用户信息 --> <delete id="deleteUserById" parameterType="int"> DELETE FROM user WHERE userId = #{id} </delete> <SQL映射规范> 同查询的规范。 2.【MyTest.java】中增加一个测试方法: <说明> 项目 解释 delete 删除处理 第一个参数 同查询 第二个参数 同查询 返回值 SQL没有返回值,但是方法本身有一个int的返回值,表示删除的记录条数。 <代码示例> // 测试根据id删除用户信息 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根据会话工厂创建会话对象 sqlSession = sqlSessionFactory.openSession(); // 根据id删除用户信息 int count = sqlSession.delete("user.deleteUserById", 1011); System.out.println("count=" + count); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.7.增删改查小结 本小结下的内容都是重点需要重点记忆。 <SQL映射规范>(需要掌握) ·参数映射规范 传单个参数时,parameterType="java简单类型",占位符中的变量可以任意名称,但不能没有。 传单个参数时,parameterType="java简单类型",拼接符中的变量名必须是value,也不能没有。 传多个参数时,parameterType="pojo类型",占位符或拼接符的变量名必须等于pojo中的属性名。 ·返回值映射规范 返回单值时,resultType="java简单类型",值直接返回给java程序。 返回单条记录时,resultType="pojo类型",结果集的列名必须等于pojo的属性名。 返回多条记录时,resultType="集合的pojo泛型的类型",结果集列名必须等于pojo泛型的属性名。 <增删改查对应的标签和java客户端调用的方法>(需要掌握) 区分 标签 客户端调用方法 增 <insert> insert(namespace名+.+sql id, sql的参数变量) 删 <delete> delete(参数同上) 改 <update> update(参数同上) 查 <select> 单值或单条selectOne(参数同上) 多条selectList(参数同上) 7.8.MyBatis对JDBC问题的解决 1.如何解决JDBC数据连接资源缺乏管理的问题? 解决:在MyBatis配置文件中配置了数据库连接池。 2.如何解决SQL的硬编码 解决:将Sql语句配置在SQL映射文件中与java代码分离。 3.如何解决SQL参数的顺序硬编码问题 解决:MyBatis的参数映射,可以帮我们把java对象自动的映射给SQL 4.如何解决结果集中字段名字符串的硬编码 解决:MyBatis的返回值映射,可以帮我们把结果集自动的映射给java对象。 7.9.MyBatis与Hibernate对比总结 7.10.插入标签中的子查询(了解) 插入的子查询主要是用来查询数据表主键的作用。查询出来的主键需要赋值给参数映射中传入的pojo对象某属性上。 7.10.1.功能一: 取得插入数据的自增主键 selectKey + LAST_INSERT_ID(),可以解决如何查询自增型主键的数据表中刚插入记录的主键的问题。先插入后取得,取得后可以和其他表做外键关联的操作。 1.【UserMapper.xml】映射文件中增加插入SQL配置 <说明> 项目 解释 <selectKey> 用于<insert>操作的子查询。 order 子查询相对于insert SQL的执行顺序(AFTER:在插入之后执行 BEFORE:在插入之前执行) keyProperty 传入的java对象参数的某个属性名,用于将子查询结果赋值给参数对象的指定属性。这样在java程序中只要执行完insert()方法,就可以从参数对象中指定的属性上取得这个子查询的结果。 resultType 子查询的值按照什么类型返回结果 LAST_INSERT_ID() mysql的函数,可以返回最新插入记录的主键,要和insert语句配合使用,否则单独执行的值就是0 <SQL示例> <!-- 插入一条用户信息并返回新插入记录的主键 --> <insert id="addUserInfo" parameterType="cn.baidu.pojo.User"> <!-- 插入操作中的子查询 --> <selectKey order="AFTER" keyProperty="userId" resultType="int"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO USER (name,mobile,sex,age,address) VALUES (#{name},#{mobile},#{sex},#{age},#{address}) </insert> 2.【MyTest.java】中增加一个测试方法: // 测试插入一条用户信息 @Test public void test1() throws Exception { SqlSession sqlSession = null; try { // 根据会话工厂创建会话对象 sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setAge(18); user.setAddress("北京"); user.setMobile("13500099000"); user.setName("张三"); user.setSex("男"); System.out.println("user.userId=" + user.getUserId()); // 插入用户信息 int count = sqlSession.insert("user.addUserInfo", user); System.out.println("count=" + count); System.out.println("user.userId=" + user.getUserId()); sqlSession.commit(); } catch(Exception ex) { ex.printStackTrace(); sqlSession.rollback(); throw ex; } finally { sqlSession.close(); } } 7.10.2.功能二: 使用UUID实现主键 selectKey + UUID(),可以解决非自增型主键的数据表中在插入数据前先创建主键的问题。 <说明> 项目 解释 <selectKey> 同上 order 同上(这里指定为BEFORE) keyProperty 同上 resultType 同上 UUID() mysql的函数,可以返回随机的UUID,可以作为主键用。 映射文件: <!-- 取得插入数据的主键后插入数据 --> <insert id="insertOrderData" parameterType="cn.baidu.pojo.Order"> <selectKey order="BEFORE" keyProperty="orderId" resultType="String"> SELECT UUID() </selectKey> INSERT INTO order1 (orderId, userId, orderStatus, goodsId, createDateTime) VALUES (#{orderId}, #{userId}, #{orderStatus}, #{goodsId}, now()); </insert> 客户端程序: // 数据库操作... // insert:表示插入SQL的方法 Order order = new Order(); order.setGoodsId("123456789"); order.setOrderStatus("01"); order.setUserId(1008); System.out.println(order.getOrderId()); ss.insert("order.insertOrderData", order); System.out.println(order.getOrderId()); ss.commit(); 8.DAO开发方法 8.1.传统DAO开发方式 传统的DAO开发方式就是编写DAO接口和DAO实现类来实现对数据库的访问。 8.1.1.编写SQL 从【UserMapper.xml】中挑选两个SQL <!-- 根据id查询用户信息 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> <!-- 根据用户名查询用户信息 --> <select id="findUserByUserName2" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> 8.1.2.编写DAO接口 在【cn.baidu.dao】包下创建DAO接口【UserDao】 package cn.baidu.dao; import java.util.List; import cn.baidu.pojo.User; public interface UserDao { // 根据id查询用户信息 public User findUserById(Integer id) throws Exception; // 根据姓名查询用户信息 public List<User> findUserByName(String name) throws Exception; } 8.1.3.编写DAO接口实现类 在【cn.baidu.dao】包下创建接口【UserDao】的实现类【UserDaoImpl】 package cn.baidu.dao; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import cn.baidu.pojo.User; public class UserDaoImpl implements UserDao { private SqlSessionFactory sqlSessionFactory; public void setSqlSessionFactory(SqlSessionFactory sqlSf) { this.sqlSessionFactory = sqlSf; } @Override public User findUserById(Integer id) throws Exception { SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); // 根据id查询 User userInfo = sqlSession.selectOne("user.findUserById", id); return userInfo; } catch(Exception ex) { ex.printStackTrace(); throw ex; } finally { sqlSession.close(); } } @Override public List<User> findUserByName(String name) throws Exception { SqlSession sqlSession = null; try { // 根据会话工厂创建会话对象 sqlSession = sqlSessionFactory.openSession(); // 根据用户名查询 List<User> userInfoList = sqlSession.selectList("user.findUserByUserName2", name); return userInfoList; } catch(Exception ex) { ex.printStackTrace(); throw ex; } finally { sqlSession.close(); } } } 8.1.4.编写客户端测试程序 在【test】目录下创建【MyTest2.java】测试程序 package mybatis; import java.io.InputStream; import java.util.List; 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 cn.baidu.dao.UserDaoImpl; import cn.baidu.pojo.User; /** * DAO开发方式 */ public class MyTest2 { private SqlSessionFactory sqlSessionFactory; private UserDaoImpl userDao; // 测试初始化函数 @Before public void init() throws Exception { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml"); // 根据主配置文件创建会话工厂 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); userDao = new UserDaoImpl(); userDao.setSqlSessionFactory(sqlSessionFactory); } // 测试根据id查询用户信息 @Test public void test1() throws Exception { User user = userDao.findUserById(1001); System.out.println(user); } // 测试根据用户名查询用户信息 @Test public void test2() throws Exception { List<User> userList = userDao.findUserListByName("迎"); System.out.println(userList); } } 8.1.5.传统DAO开发方法的问题 正常的传统DAO接口的实现类中各个方法的逻辑基本相同,代码重复的部分较多。除非有特殊业务要求才会加入特殊的业务逻辑,否则实现类中的方法几乎一致。 8.2.MyBatis动态代理DAO开发方式(重点) 8.2.1.什么是MyBatis动态代理 MyBatis打破传统DAO的开发方式,不需要程序员再写DAO的实现类了,可以直接用DAO接口的对象调用数据库处理的方法,MyBatis在执行时由代理对象代替DAO接口的实现类执行数据库处理。 要使用MyBatis动态代理就必须遵守动态代理的开发规范,即四个相等。 8.2.2.MyBatis动态代理开发规范 (需要掌握) 接口 映射文件 完全限定名 = Namespace的值 方法名 = SQL的id的值 接口方法的参数类型 = parameterType的值 接口方法的返回值类型 = resultType的值 8.2.3.编写SQL映射文件并添加配置 在【cn.baidu.mapper】包下创建新的映射文件【UserMapper2.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:整个MyBatis管理的映射文件中必须唯一 --> <mapper namespace="cn.baidu.dao.UserDao2"> <!-- SQL --> <!-- 根据id查询用户信息 --> <select id="findUserById" parameterType="int" resultType="cn.baidu.pojo.User"> SELECT userId, name, mobile, sex, age, address FROM user WHERE userId = #{userId} </select> <!-- 根据用户名查询用户信息 --> <select id="findUserByUserName" parameterType="String" resultType="cn.baidu.pojo.User"> SELECT userId, name,mobile,sex,age,address FROM user WHERE name LIKE '%${value}%' </select> </mapper> 8.2.4.编写DAO接口 在【cn.baidu.dao】包下创建DAO接口【UserDao2】 package cn.baidu.dao; import java.util.List; import cn.baidu.pojo.User; public interface UserDao2 { // 根据id查询用户信息 public User findUserById(Integer id) throws Exception; // 根据姓名查询用户信息 public List<User> findUserByUserName(String name) throws Exception; } 8.2.5.编写客户端测试程序 在【MyTest2.java】测试程序中增加两个测试方法 <说明> 项目 解释 getMapper 生成接口的代理对象。 参数 接口的类型描述。(Xxxx.class) 返回值 接口类型的代理对象。 <示例代码> // 测试根据id查询用户信息(动态代理DAO开发方式) @Test public void test3() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 用getMapper取得自动生成的DAO接口的实现类 UserDao2 userDao = sqlSession.getMapper(UserDao2.class); User userInfo = userDao.findUserById(1001); System.out.println(userInfo); sqlSession.close(); } // 测试根据id查询用户信息(动态代理DAO开发方式) @Test public void test4() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserDao2 userDao = sqlSession.getMapper(UserDao2.class); List<User> userList = userDao.findUserByUserName("迎"); System.out.println(userList); sqlSession.close(); } 8.3.动态代理DAO开发方式的好处 Mybatis已经把开发中能简化的都简化了,对于我们开发人员就可以好好的集中精力写SQL了。 8.4.传统DAO开发方式与MyBatis动态代理的区别 传统方式需要编写DAO接口的实现类,并通过实例化实现类的对象来访问数据库。 动态代理不需编写DAO接口的实现类,并通过接口类的代理对象来访问数据库。代理对象由MyBatis自动生成。 8.5.小结 本章的重点是MyBatis动态代理DAO开发方式,要掌握如何开发动态代理DAO,牢记动态代理四个开发规范。这也是今天课程中第二个重点。 9.MyBatis核心配置文件 9.1.核心配置文件配置项结构 官方说明Url:http://www.mybatis.org/mybatis-3/zh/configuration.html ·注意:配置项必须按照上面的顺序配置 红框中的为常用配置项,需要知道,其他配置项不常用,今后工作中用到时再查资料了解。 9.2.properties(属性)配置项 9.2.1.properties文件的好处 1.容易维护:相比于xml,properties文件更易于修改,降低修改时出错的几率。在实际项目中经常把properties属性文件与xml配置文件结合使用,把真正的值都放在properties属性文件中,在xml中使用的时候直接引过来就可以使用了,非常方便。 2.一处修改多处生效 9.2.2.properties标签配置 <!-- 属性文件的配置 --> <!-- properties:表示属性文件配置的标签 resource:表示类的相对路径下的java属性文件 url:表示文件的绝对路径 --> <properties resource="jdbc.properties" /> 对应数据源配置的修改: <!-- 数据库环境的配置 --> <environments default="dev"> <!-- 开发数据库环境的配置 --> <environment id="dev"> <!-- 事务管理的配置 --> <transactionManager type="JDBC"/> <!-- 数据源配置:driver, url, username, password --> <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> </environments> properties属性文件中key的命名方式: 文件名 + . + 具体的key名,这样做的好处是不容易造成两个properties文件中出现同名的key。key一旦同名,这两个properties文件又都被同时引入到一个xml中就会出错。 9.3.typeAliases(类型别名)配置项 9.3.1.MyBatis内建的java类型的别名 注意: 红色框中的才是java基本类型以及对应的别名,从中可以看出java基本类型的别名都是以下划线开头的。 蓝色框中的是java基本类型的包装类以及对应的别名,这也是我们前面一直在使用的。 没有画框的都是正常使用的别名。 为什么在MyBatis内建议使用java基本类型的包装类型来传递参数? MyBatis在处理参数时,如果传递的是java基本类型:int、long、char、bool等,MyBatis都会将基本类型包装成:Integer、Long、String、Boolean等,然后再进行传递,这样做的好处是避免java基本类型的默认值问题。Java基本类型参数在没有实际赋值时都会有默认值,如果你不主动给参数变量赋值就直接传给SQL,就会把参数变量的默认值传给SQL语句,这样就可能造成破坏业务数据的风险,因此在MyBatis内部这样的参数都会自动的被包装成对象进行传值。对象的好处是一旦没有传值,由于是对象,它的默认值就是null,给SQL传递null时一般都不会执行成功。 因此, 为了数据安全尽量使用蓝框的包装类型来传值赋值. 9.3.2.别名都是大小写不敏感的 int和INT、iNt、Int等都是一样的,其它的也一样不区分大小写 9.3.3.别名的目的 简化映射文件的配置,缩短配置项的长度 9.3.4.POJO类型的自定义别名配置格式 MyBatisConfig.xml中(注意主配置文件中的项目是有顺序的) <!-- 自定义别名 --> <typeAliases> <typeAlias type="cn.baidu.pojo.User" alias="User"/> </typeAliases> 映射文件: <!-- 根据id查询用户信息 --> <select id="findUserById" parameterType="int" resultType="User"> select userId, name,mobile,sex,age,address FROM user WHERE userId = #{userId} </select> 9.3.5.结论 对于java本身拥有的类型可以使用别名,而自定义类型不要使用别名。 推荐使用java包装类型或它们的别名(篮框中的),而不要使用java基本类型和它们的别名(红框中的)。 9.4.mappers(映射文件)配置项 9.4.1.映射文件配置形式一 直接指定映射文件在类根目录下的相对路径 <说明> 项目 解释 <mappers> 用于SQL映射文件的配置,下面可以配置多个映射文件的扫描 <mapper> 是<mappers>的子标签,用于一个映射文件的配置 resource 映射文件相对于类根目录下的相对路径 适用范围 两种DAO开发方式都适用这种配置,DAO的路径和SQL映射文件的路径之间没有任何联系,随意定义。 配置: <!-- 配置映射文件 --> <mappers> <mapper resource="cn/baidu/mapper/UserMapper.xml"/> <mapper resource="cn/baidu/mapper/UserMapper2.xml"/> </mappers> 9.4.2.映射文件配置形式二 重新创建一个包【cn.baidu.mapper2】,在其中创建两个映射文件和两个对应同名的DAO接口,然后进行试验。 通过一个java接口的完全限定名加载映射文件 <说明> 项目 解释 class 接口的完全限定名 要求 DAO接口文件与映射文件必须同名同目录 适用范围 只适用于MyBatis动态代理DAO开发方式 配置: <!-- 配置映射文件 --> <mappers> <mapper class="cn.baidu.mapper2.UserMapper" /> </mappers> 9.4.3.映射文件配置形式三 <说明> 项目 解释 <package> 是第二种形式的批量加载形式 name 指定扫描的包路径 要求 指定包下的DAO接口文件与映射文件必须同名同目录 适用范围 只适用于MyBatis动态代理DAO开发方式 配置: <!-- 配置映射文件 --> <mappers> <package name="cn.baidu.mapper2"/> </mappers> 9.4.4.三种形式的辨析 1.形式一: 两种DAO开发方式都使用,但比较死板,一旦路径写错了不容易发现。一次只能加载一个映射文件。 2.形式二: 只适用于动态代理方式,比较灵活,检验是否写错很容易(按住ctrl键,鼠标指针放上去有连接说明写的没有错)。SM整合后可以放到spring容器中。 3.形式三: 只适用于动态代理方式,方式更加实用,是形式二的批量扫描方式,比形式二好在可以批量加载,SM整合后可以放到spring容器中。