本文记录了mybatis中基础知识的学习,下一篇会介绍mybatis的高级用法,相关代码和参考资料在这里,下面正式开始学习
1.原生态JDBC程序中的问题总结
- 传统使用JDBC对java文件与数据库进行连接的步骤和代码如下:
首先在导入jdbc驱动包之后,创建数据库并编写数据库连接代码
package jdbc_test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import com.mysql.jdbc.Driver; public class jdbcTest { public static void main(String[] args) throws ClassNotFoundException, SQLException { // TODO Auto-generated method stub // 数据库的连接 Connection connection = null; // 预编译的Statement,使用预编译的Statement提高数据库的性能 // 通过预编译的Statement向数据库发生sql语句,数据库要对sql语句进行编译,编译完之后数据库会对得到的结果集存到数据库端的缓存中 // 下一次再发送相同的sql语句,数据库则不用重新编译,直接从缓存中拿来结果集即可,所以提高了数据库的性能。 PreparedStatement preparedStatement = null; // 结果集 ResultSet resultSet = null; try { // 加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 通过驱动管理类获取数据库连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_learning?useSSL=false", "root", "7458"); // 定义sql语句,其中?表示占位符 String sqlString = "select * from user where username = ?"; // 获取预处理statement preparedStatement = connection.prepareStatement(sqlString); // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五"); // 向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { System.out.println(resultSet.getString("id") + " " + resultSet.getString("username")); } } finally { // TODO: handle finally clause // 释放资源(倒着释放) if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { // TODO: handle exception e.printStackTrace(); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { // TODO: handle exception e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { // TODO: handle exception e.printStackTrace(); } } } } }
- 分析问题:
- 数据库的连接,使用时就创建,不使用时立即释放,对数据库进行频繁的连接开启和关闭,造成数据库资源的浪费,影响数据库的性能;
- 设想:使用数据库连接池管理数据库的连接。
- 将sql语句硬编码到java文件中,如果sql语句修改,需要重新编译java代码,不利于系统维护。
- 设想:将sql语句配置到xml文件中,即使sql变化,不需要对java代码进行重新编译。
- 向preparedStatement中设置参数,对占位符设置和参数值的设置同样硬编码到java代码中,不利于系统维护。
- 设想:将sql语句及占位符号和参数全部配置到xml中。
- 从resultSet中遍历结果集数据时,也存在硬编码,将获取表的字段进行硬编码,不利于系统维护。
- 设想:将查询的结果集自动映射成java对象。
- 数据库的连接,使用时就创建,不使用时立即释放,对数据库进行频繁的连接开启和关闭,造成数据库资源的浪费,影响数据库的性能;
2.mybatis框架
-
什么是mybatis
mybatis是一个持久层的框架,是apache下的顶级项目,现在被托管到github上,它可以让程序将主要的精力放在sql上,通过mybatis提供的映射方式,自由灵活的生成(半自动化,大部分需要程序员编写sql)满足需要的sql语句,将preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射)。
-
mybatis的框架结构
3.mybatis的工程结构及入门增删改查的开发步骤
- 设置全局配置文件:mybatis-config.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配置将废除 --> <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_learning?useSSL=false" /> <property name="username" value="root" /> <property name="password" value="7458" /> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <mapper resource="com/basicOp/mapper/UserMapper.xml" /> </mappers> </configuration>
- 设置日志文件:log4j.properties
# Global logging configuration # 在开发环境下日志级别是DEBUG,生产环境设置为info或error log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- 定义自定义pojo对象类型,要与数据库中的数据类型对应
package com.basicOp.pojo; import java.util.Date; public class User { private int id; private String username;// 用户姓名 private String sex;// 性别 private Date birthday;// 生日 private String address;// 地址 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address=" + address + "]"; } }
- 设置mapper文件,准备操作数据库,其命名格式按照UserMapper.xml这种与pojo实体类关联的驼峰格式,这样的好处是代理可以自动识别。最后将映射文件加入到全局配置文件中。
<?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表示命名空间,每一个命名空间代表着一个pojo的映射器 --> <mapper namespace="com.basicOp.mapper.UserMapper"> <!-- id:标识映射文件中的sql,sql语句封装到mappedStatement对象中,所以将id称为statement的id parameterType:指定输入参数的类型 resultType:不管返回是单条还是多条记录,其指定的的单条sql输出结果所映射的java对象类型 #{}:表示占位符 --> <select id="findUserById" parameterType="int" resultType="com.basicOp.pojo.User"> select * from user where id = #{id} </select> <select id="findUserByName" parameterType="string" resultType="com.basicOp.pojo.User"> select * from user where username like concat('%', #{username}, '%') </select> <!-- parameterType在插入时是对应的pojo对象类型 #{}里面是pojo的属性值,mybatis通过OGNL获取对象的属性值 --> <insert id="insertUser" parameterType="com.basicOp.pojo.User"> insert into user (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address}) </insert> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id = #{id} </delete> <update id="updateUser" parameterType="com.basicOp.pojo.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update> </mapper>
- 创建与mapper所对应的接口文件,命名按照上面的mapper文件一样UserMapper.java,里面包括了操作数据库的方法,方法名与mapper中的id是一样的。
package com.basicOp.mapper; import java.util.List; import com.basicOp.pojo.User; public interface UserMapper { public User findUserById(int id); public List<User> findUserByName(String userName); public int insertUser(User user); public int deleteUser(int id); public int updateUser(User user); }
- 按照框架编写程序,包括SqlSessionFactory、SqlSession等,将创建SqlSessionFactory和SqlSession直接放到一个工具类包里,后续在测试类中可直接调用。
package com.basicOp.utils; 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; public class SqlSessionFactoryUtils { public static final Class<SqlSessionFactoryUtils> LOCK = SqlSessionFactoryUtils.class; private static SqlSessionFactory sqlSessionFactory = null; private SqlSessionFactoryUtils() { } public static SqlSessionFactory getSqlSessionFactory() { synchronized (LOCK) { if (sqlSessionFactory != null) { return sqlSessionFactory; } try { //获得mybatis配置文件 String resource = "mybatis-config.xml"; //得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); //通过配置文件流创建会话工厂,传入mabatis的配置文件信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { // TODO: handle exception e.printStackTrace(); } return sqlSessionFactory; } } public static SqlSession openSqlSession() { if (sqlSessionFactory == null) { getSqlSessionFactory(); } //通过工厂得到SqlSession return getSqlSessionFactory().openSession(); } }
- 编写测试类
package com.basicOp.main; import java.util.Date; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.apache.log4j.Logger; import com.basicOp.mapper.UserMapper; import com.basicOp.pojo.User; import com.basicOp.utils.SqlSessionFactoryUtils; public class basicOpMain { public static void main(String[] args) { // TODO Auto-generated method stub // testFindUserById(); // testFindUserByName(); // testInsertUser(); // testDeleteUser(); testUpdateUser(); } // 获得通过id查询得到的User结果 public static void testFindUserById() { Logger logger = Logger.getLogger(basicOpMain.class); SqlSession sqlSession = null; try { // 创建会话 sqlSession = SqlSessionFactoryUtils.openSqlSession(); // 利用sqlSession获得对应的mapper接口后,开始操作数据库 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.findUserById(1); logger.info(user.toString()); } finally { // TODO: handle finally clause if (sqlSession != null) { sqlSession.close(); } } } // 利用username进行模糊查询 public static void testFindUserByName() { Logger logger = Logger.getLogger(basicOpMain.class); SqlSession sqlSession = null; try { // 创建会话 sqlSession = SqlSessionFactoryUtils.openSqlSession(); // 利用sqlSession获得对应的mapper接口后,开始操作数据库 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.findUserByName("小明"); for (User user : users) { logger.info(user.toString()); } } finally { // TODO: handle finally clause if (sqlSession != null) { sqlSession.close(); } } } // 插入数据 public static void testInsertUser() { Logger logger = Logger.getLogger(basicOpMain.class); SqlSession sqlSession = null; try { // 创建会话 sqlSession = SqlSessionFactoryUtils.openSqlSession(); // 利用sqlSession获得对应的mapper接口后,开始操作数据库 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setUsername("许昕"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("中国徐州"); userMapper.insertUser(user); // 提交事务 sqlSession.commit(); logger.info(user); } finally { // TODO: handle finally clause if (sqlSession != null) { sqlSession.close(); } } } // 删除数据 public static void testDeleteUser() { Logger logger = Logger.getLogger(basicOpMain.class); SqlSession sqlSession = null; try { // 创建会话 sqlSession = SqlSessionFactoryUtils.openSqlSession(); // 利用sqlSession获得对应的mapper接口后,开始操作数据库 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.deleteUser(28); // 提交事务 sqlSession.commit(); } finally { // TODO: handle finally clause if (sqlSession != null) { sqlSession.close(); } } } // 更新数据 public static void testUpdateUser() { Logger logger = Logger.getLogger(basicOpMain.class); SqlSession sqlSession = null; try { // 创建会话 sqlSession = SqlSessionFactoryUtils.openSqlSession(); // 利用sqlSession获得对应的mapper接口后,开始操作数据库 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(27); user.setUsername("许昕"); user.setAddress("江苏徐州"); userMapper.updateUser(user); // 提交事务 sqlSession.commit(); } finally { // TODO: handle finally clause if (sqlSession != null) { sqlSession.close(); } } } }
4.myBatis开发dao方法(mapper代理的方法)
- SqlSession使用范围
- SqlSessionFactoryBuilder
通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory,将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder,在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。
- SqlSessionFactory
通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例),将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。
- SqlSession
SqlSession是一个面向用户(程序员)的接口,SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象),SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。所以SqlSession最佳应用场合在方法体内,定义成局部变量使用。
- SqlSessionFactoryBuilder
- 原始dao开发的问题
在myBatis中若按照原始的dao方法进行开发,首先要定义好mapper映射文件中每个功能的接口,再实现该接口,然后进行测试,这就会出现如下问题:
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
- 调用SqlSession方法时将statement的id硬编码了。
- 调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
使用mapper代理的开发,在编写完mapper.xml映射文件后,只需要编写对应mapper接口即可,myBatis可以自动根据接口利用反射实现类的代理对象,mapper接口中的接口名、方法名、参数名、返回值要与mapper文件中对应。
5.全局配置文件mybatis-config.xml中的元素属性
在全局配置文件中,元素属性的设置是有顺序的,要按照如下顺序进行设置和定义:
-
properties属性
将数据库的连接参数单独的设置在jdbc.properties中,只需要在全局配置文件中加载jdbc.properties的属性值即可,避免了在全局配置文件中的硬编码,方便统一管理。
注意: MyBatis 将按照下面的顺序来加载属性:
- 在 properties 元素体内定义的属性首先被读取。
- 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
- 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
建议:不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
-
setting全局参数配置
mybatis框架在运行时可以调整一些运行参数,比如:开启二级缓存、开启延迟加载等,会影响全局的运行行为,需要的时候再使用。
-
typeAliases别名属性
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
mybatis默认支持别名:
也可以进行自定义别名设置,包括单个别名设置和批量别名设置。
-
typeHandlers类型处理器
mybatis中通过typeHandlers完成jdbc类型和java类型的转换,通常情况下mybatis提供的类型处理器满足日常需要,不需要自定义。
-
objectFactory对象工程和plugins插件一般不用,用的时候查看开发手册即可,environments环境属性即将要被废弃。
-
mapper映射器
可以通过resource加载单个映射文件;
可以通过mapper接口加载单个mapper;
可以批量加载mapper;
6.输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo包装类型,具体使用方法参考使用手册,pojo包装类型很重要,会比较常用。
7.输出映射
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功,如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间做一个映射关系,其他简单的返回的具体使用方法参考使用手册。
8.动态sql
动态sql可以使得对sql语句的使用更加灵活,通过表达式进行判断,对sql进行灵活拼接、组装,具体使用方法可参考使用手册。