Mybatis高级学习笔记 学习来源:黑马程序员
学习时间:2021年3月9日,2023年6月24日
1 什么是框架 1.1 框架概念 它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。框架封装了很多细节,是开发者可以使用极简的方式实现功能。大大提高开发效率。
1.2 三层架构
表现层:用于展示数据。
业务层:用于处理业务需求。
持久层:用于和数据库交互。
1.3 持久层技术解决方案
JDBC技术:Connection,PreparedStatement,ResultSet
Spring的JdbcTemplate:Spring对Jdbc的简单封装
Apache的DBUtils:也是对jdbc的简单封装
以上都不是框架,jdbc是规范,下面两者都只是工具类。
2 Mybatis框架概述 mybatis是一个优秀的基于Java的持久层框架
,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动,创建连接,创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。它使用了ORM思想,实现了结果集的封装。
ORM-Object Relational Mapping,对象关系映射。就是把数据库表和实体类的属性对应起来,让我们可以操作实体类就可以实现操作数据库表。
3 Mybatis入门 3.1 环境搭建 3.1.1 步骤
导入坐标
1 2 3 4 5 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.5</version > </dependency >
创建实体类和dao的接口
user.java
代码
1 2 3 4 5 6 7 8 9 public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; }
IUserDao.java
代码
1 2 3 4 5 6 7 8 9 10 public interface IUserDao { List<User> findAll () ; }
创建mybatis的主配置文件
SqlMapConfig.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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 ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/test" /> <property name ="username" value ="root" /> <property name ="password" value ="12345678" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/hongyi/dao/IUserDao.xml" > </mapper > </mappers > </configuration >
创建映射配置文件
IUserDao.xml
1 2 3 4 5 6 7 8 9 10 11 12 <?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 ="com.hongyi.dao.IUserDao" > <select id ="findAll" resultType ="com.hongyi.domain.User" > select * from user </select > </mapper >
3.1.2 注意事项
创建IUserDao.xml和IUserDao.java时,在mybatis中,它把持久层的操作接口名称和映射文件也叫作Mapper。所以,IUserDao和IUserMapper是一样的。
在IDEA中创建目录的时候,它和包是不一样的。包在创建时:com.hongyi.dao时三级结构,而目录在创建时是一级目录。要一级一级地创建。
mybatis的映射配置文件位置必须与dao接口的包结构相同 。
映射配置文件的mapper标签中的namespace
属性的取值,必须是dao接口的全限定类名
。
映射配置文件的操作配置,id属性的取值,必须是dao接口的方法名。
当我们遵从了第三四五点之后,在开发中就无须再写dao的实现类
,剩下的就由mybatis来实现。
3.1.3 项目结构图
3.2 入门案例 3.2.1 步骤
读取配置文件
创建SqlSessionFactory工厂
创建SqlSession
创建Dao接口的代理对象
执行dao中的方法
释放资源
注意:不要忘记在映射配置中告知mybatis要封装到哪个实体类中,配置的方法是指定实体类的全限定类名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class MybatisTest { public static void main (String[] args) throws IOException { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder (); SqlSessionFactory factory = builder.build(in); SqlSession session = factory.openSession(); IUserDao userDao = session.getMapper(IUserDao.class); List<User> users = userDao.findAll(); for (User user : users){ System.out.println(user); } session.close(); in.close(); } }
3.2.2 注解开发 把IUserDao.xml
移除,在dao接口的方法上使用@Select
注解,并指定sql语句,同时需要在SqlMapConfig.xml
中的mapper
配置时,使用class
属性指定dao接口的全限定类名 。
1 2 @Select("select * from user") List<User> findAll () ;
1 2 3 4 <mappers > <mapper class ="com.hongyi.dao.IUserDao" > </mapper > </mappers >
我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式,不管使用xml还是注解配置。但是mybatis是支持写dao实现类的。
3.2.3 设计模式分析
创建工厂SqlSessionFactory
使用了建造者模式 :
1 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
生产SqlSession
使用了工厂模式 :
1 SqlSession sqlSession = sqlSessionFactory.openSession(true );
创建userDao
(IUserDao
接口的实现类对象)使用了动态代理 。
1 IUserDao userDao = session.getMapper(IUserDao.class);
4 自定义Mybatis 4.1 查询所有的分析
mybatis在使用代理dao的方式实现增删改查时做了什么事呢?
创建代理对象
在代理对象中调用selectList
(1)连接数据库的信息,有了它们就能创建Connection对象
1 2 3 4 5 <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/test?characterEncoding=utf8" /> <property name ="username" value ="root" /> <property name ="password" value ="12345678" />
1 2 3 <mappers > <mapper class ="com.hongyi.dao.IUserDao" > </mapper > </mappers >
(3)有了它就有了执行的Sql语句,就可以获取PreparedStatement
。此配置中还有封装的实体类的全限定类名
1 2 3 4 5 <mapper namespace ="com.hongyi.dao.IUserDao" > <select id ="findAll" resultType ="com.hongyi.domain.User" > select * from user </select > </mapper >
1+2+3:读取配置文件:用到的技术就是解析xml的技术。此种用的是dom4j
解析xml技术。
selectList
方法执行的伪代码过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 注册驱动,获取连接 此时需要Sql语句 con.preparedStatement(sql); ResuluSet resultSet = preparedStatement.executeQuery(); List<E> list = new ArrayList (); while (resultSet.next()){ E element = (E)Class.forName(全限定类名).newInstance(); 进行封装,把resultSet的内容都添加到element中; 把element加入到list中; list.add(element); } return list;
要想让selectList
方法执行,我们需要给方法提供两个信息:
对于映射信息的封装
它包含了两部分:执行的sql语句;封装结果的实体类全限定类名。把这两个信息组合起来定义成一个对象
。
该对象(假设名为Mapper
)与键为String类型的全限定类名+方法名(例如com.hongyi.dao.IUserDao.findAll
)绑定,形成一个Map。
4.2 创建代理对象的分析 1 2 IUserDao userDao = session.getMapper(IUserDao.class);
过程分析:
1 2 3 4 5 6 7 public <T> T getMapper (Class<T> daoInterfaceClass) { Proxy.newProxyInstance(类加载器,代理对象要实现的接口字节码数组,如何代理); }
4.3 自定义mybatis的编码 4.3.1 初始化框架搭建 自定义mybatis需要的四个类
class Resources
class SqlSessionFactoryBuilder
interface SqlSessionFactory
interface SqlSession
Resources.java
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Resources { public static InputStream getResourceAsStream (String filePath) { return Resources.class.getClassLoader().getResourceAsStream(filePath); } }
SqlSessionFactoryBuilder.java
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SqlSessionFactoryBuilder { public SqlSessionFactory build (InputStream config) { return null ; } }
SqlSessionFactory.java
1 2 3 4 5 6 7 public interface SqlSessionFactory { SqlSession openSession () ; }
SqlSession.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface SqlSession { <T> T getMapper (Class<T> daoInterfaceClass) ; void close () ; }
<T> T
的含义:<T> T
表示返回值T
的类型是泛型
4.3.2 解析xml的工具类 工具类XMLConfigBuilder.java
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 public class XMLConfigBuilder { public static Configuration loadConfiguration (InputStream config) { try { Configuration cfg = new Configuration (); SAXReader reader = new SAXReader (); Document document = reader.read(config); Element root = document.getRootElement(); List<Element> propertyElements = root.selectNodes("//property" ); for (Element propertyElement : propertyElements){ String name = propertyElement.attributeValue("name" ); if ("driver" .equals(name)){ String driver = propertyElement.attributeValue("value" ); cfg.setDriver(driver); } if ("url" .equals(name)){ String url = propertyElement.attributeValue("value" ); cfg.setUrl(url); } if ("username" .equals(name)){ String username = propertyElement.attributeValue("value" ); cfg.setUsername(username); } if ("password" .equals(name)){ String password = propertyElement.attributeValue("value" ); cfg.setPassword(password); } } List<Element> mapperElements = root.selectNodes("//mappers/mapper" ); for (Element mapperElement : mapperElements){ Attribute attribute = mapperElement.attribute("resource" ); if (attribute != null ){ System.out.println("使用的是XML" ); String mapperPath = attribute.getValue(); Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath); cfg.setMappers(mappers); }else { } } return cfg; }catch (Exception e){ throw new RuntimeException (e); }finally { try { config.close(); }catch (Exception e){ e.printStackTrace(); } } } private static Map<String,Mapper> loadMapperConfiguration (String mapperPath) throws IOException { InputStream in = null ; try { Map<String,Mapper> mappers = new HashMap <String,Mapper>(); in = Resources.getResourceAsStream(mapperPath); SAXReader reader = new SAXReader (); Document document = reader.read(in); Element root = document.getRootElement(); String namespace = root.attributeValue("namespace" ); List<Element> selectElements = root.selectNodes("//select" ); for (Element selectElement : selectElements){ String id = selectElement.attributeValue("id" ); String resultType = selectElement.attributeValue("resultType" ); String queryString = selectElement.getText(); String key = namespace+"." +id; Mapper mapper = new Mapper (); mapper.setQueryString(queryString); mapper.setResultType(resultType); mappers.put(key,mapper); } return mappers; }catch (Exception e){ throw new RuntimeException (e); }finally { in.close(); } } }
4.3.3 SqlSessionFactory和SqlSession的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Configuration { private String driver; private String url; private String username; private String password; private Map<String,Mapper> mappers = new HashMap <>(); public Map<String, Mapper> getMappers () { return mappers; } public void setMappers (Map<String, Mapper> mappers) { this .mappers.putAll(mappers); } }
1 2 3 4 5 6 7 8 @Data public class Mapper { private String queryString; private String resultType; }
接口SqlSessionFactory
的实现类DefaultSqlSessionFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration cfg; public DefaultSqlSessionFactory (Configuration cfg) { this .cfg = cfg; } @Override public SqlSession openSession () { return new DefaultSqlSession (cfg); } }
接口SqlSession
的实现类DefaultSqlSession
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class DefaultSqlSession implements SqlSession { private Configuration cfg; public DefaultSqlSession (Configuration cfg) { this .cfg = cfg; } @Override public <T> T getMapper (Class<T> daoInterfaceClass) { return null ; } @Override public void close () { } }
4.3.4 基于XML的查询所有操作 代码整体框架:
① DefaultSqlSession 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class DefaultSqlSession implements SqlSession { private Configuration cfg; private Connection conn; public DefaultSqlSession (Configuration cfg) { this .cfg = cfg; conn = DataSourceUtil.getConnection(cfg); } @Override public <T> T getMapper (Class<T> daoInterfaceClass) { return (T) Proxy.newProxyInstance( daoInterfaceClass.getClassLoader(), new Class []{daoInterfaceClass}, new MapperProxy (cfg.getMappers(), conn)); } @Override public void close () { if (conn != null ) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }
② DataSourceUtil 通过数据源(连接池)获取一个sql连接对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class DataSourceUtil { public static Connection getConnection (Configuration cfg) { try { Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword()); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); throw new RuntimeException (e); } } }
③ MapperProxy MapperProxy
是一个实现接口InvocationHandler
的类,它必须实现invoke
方法(即调用被代理对象的同名方法),以完成代理的具体操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MapperProxy implements InvocationHandler { private Map<String, Mapper> mappers; private Connection conn; public MapperProxy (Map<String, Mapper> mappers, Connection conn) { this .mappers = mappers; this .conn = conn; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className + "." + methodName; Mapper mapper = mappers.get(key); if (mapper == null ) { throw new IllegalArgumentException ("传入的参数有误" ); } return new Executor ().selectList(mapper, conn); } }
④ Executor 执行mapper对应的方法,并返回封装后的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class Executor { public <E> List<E> selectList (Mapper mapper, Connection conn) { PreparedStatement pstm = null ; ResultSet rs = null ; try { String queryString = mapper.getQueryString(); String resultType = mapper.getResultType(); Class domainClass = Class.forName(resultType); pstm = conn.prepareStatement(queryString); rs = pstm.executeQuery(); List<E> list = new ArrayList <E>(); while (rs.next()) { E obj = (E) domainClass.newInstance(); ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); for (int i = 1 ; i <= columnCount; i++) { String columnName = rsmd.getColumnName(i); Object columnValue = rs.getObject(columnName); PropertyDescriptor pd = new PropertyDescriptor (columnName,domainClass); Method writeMethod = pd.getWriteMethod(); writeMethod.invoke(obj,columnValue); } list.add(obj); } return list; } catch (Exception e) { throw new RuntimeException (e); } finally { release(pstm,rs); } } private void release (PreparedStatement pstm,ResultSet rs) { if (rs != null ){ try { rs.close(); }catch (Exception e){ e.printStackTrace(); } } if (pstm != null ){ try { pstm.close(); }catch (Exception e){ e.printStackTrace(); } } } }
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 使用的是XML {com.hongyi.dao.IUserDao.findAll=Mapper(queryString= SELECT * FROM user , resultType=com.hongyi.domain.User)} User(id=1, username=Action, birthday=2006-02-15 04:46:27.0) User(id=2, username=Animation, birthday=2006-02-15 04:46:27.0) User(id=3, username=Children, birthday=2006-02-15 04:46:27.0) User(id=4, username=Classics, birthday=2006-02-15 04:46:27.0) User(id=5, username=Comedy, birthday=2006-02-15 04:46:27.0) User(id=6, username=Documentary, birthday=2006-02-15 04:46:27.0) User(id=7, username=Drama, birthday=2006-02-15 04:46:27.0) User(id=8, username=Family, birthday=2006-02-15 04:46:27.0) User(id=9, username=Foreign, birthday=2006-02-15 04:46:27.0) User(id=10, username=Games, birthday=2006-02-15 04:46:27.0) User(id=11, username=Horror, birthday=2006-02-15 04:46:27.0) User(id=12, username=Music, birthday=2006-02-15 04:46:27.0) User(id=13, username=New, birthday=2006-02-15 04:46:27.0) User(id=14, username=Sci-Fi, birthday=2006-02-15 04:46:27.0) User(id=15, username=Sports, birthday=2006-02-15 04:46:27.0) User(id=16, username=Travel, birthday=2006-02-15 04:46:27.0) Process finished with exit code 0
4.3.5 基于注解的查询所有操作 ① 配置文件 将resource
修改为class
,并将属性修改为全限定类型:
1 2 3 <mappers > <mapper class ="com.hongyi.dao.IUserDao" /> </mappers >
② IUserDao 在方法上添加注解:
1 2 3 4 5 6 7 8 public interface IUserDao { @Select("select * from user") List<User> findAll () ; }
③ Select 在mybatis
下新建包Annotation
,并新建注解Select
:
1 2 3 4 5 6 7 8 9 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Select { String value () ; }
④ XMLConfigBuilder 放开所有注解即可。
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 使用的是注解 User(id=1, username=Action, birthday=2006-02-15 04:46:27.0) User(id=2, username=Animation, birthday=2006-02-15 04:46:27.0) User(id=3, username=Children, birthday=2006-02-15 04:46:27.0) User(id=4, username=Classics, birthday=2006-02-15 04:46:27.0) User(id=5, username=Comedy, birthday=2006-02-15 04:46:27.0) User(id=6, username=Documentary, birthday=2006-02-15 04:46:27.0) User(id=7, username=Drama, birthday=2006-02-15 04:46:27.0) User(id=8, username=Family, birthday=2006-02-15 04:46:27.0) User(id=9, username=Foreign, birthday=2006-02-15 04:46:27.0) User(id=10, username=Games, birthday=2006-02-15 04:46:27.0) User(id=11, username=Horror, birthday=2006-02-15 04:46:27.0) User(id=12, username=Music, birthday=2006-02-15 04:46:27.0) User(id=13, username=New, birthday=2006-02-15 04:46:27.0) User(id=14, username=Sci-Fi, birthday=2006-02-15 04:46:27.0) User(id=15, username=Sports, birthday=2006-02-15 04:46:27.0) User(id=16, username=Travel, birthday=2006-02-15 04:46:27.0)
5 Mybatis框架实现CRUD操作 5.1 自定义Mybatis的流程分析
5.2 CRUD操作 5.2.1 保存操作 IUserDao.xml
1 2 3 4 <insert id ="saveUser" parameterType ="com.hongyi.domain.User" > insert into user(username,address,sex,birthday) values (#{username},#{address},#{sex},#{birthday}); </insert >
IUserDao.java
1 2 3 4 5 void saveUser (User user) ;
改进后的mybatis测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MybatisTest { private InputStream in; private SqlSession session; private IUserDao userDao; @Before public void init () throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder (); SqlSessionFactory factory = builder.build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws IOException { session.commit(); session.close(); in.close(); } @Test public void testFindAll () throws IOException { List<User> users = userDao.findAll(); for (User user : users){ System.out.println(user); } } @Test public void testSave () throws IOException { User user = new User (); user.setUsername("曾弘毅" ); user.setAddress("中国" ); user.setSex("男" ); user.setBirthday(new Date ()); userDao.saveUser(user); } }
5.2.2 更新和删除操作 IUserDao.xml
1 2 3 4 5 6 7 8 9 10 <update id ="updateUser" parameterType ="com.hongyi.domain.User" > update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update > <delete id ="deleteUser" parameterType ="java.lang.Integer" > delete from user where id=#{id} </delete >
IUserDao.java
1 2 3 4 5 6 7 8 9 10 void updateUser (User user) ;void deleteUser (Integer id) ;
MybatisTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testUpdate () throws IOException { User user = new User (); user.setId(53 ); user.setUsername("孙文" ); user.setAddress("台湾" ); user.setSex("男" ); user.setBirthday(new Date ()); userDao.updateUser(user); } @Test public void testDelete () throws IOException { userDao.deleteUser(53 ); }
5.2.3 查询和模糊查询 IUserDao.java
1 2 3 4 5 6 7 8 9 10 11 12 13 User findById (Integer id) ; List<User> findByName (String username) ;
IUserDao.xml
1 2 3 4 5 6 7 8 9 <select id ="findById" parameterType ="INT" resultType ="com.hongyi.domain.User" > select * from user where id=#{id}; </select > <select id ="findByName" parameterType ="java.lang.String" resultType ="com.hongyi.domain.User" > select * from user where username like #{username}; </select >
MybatisTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void testFindOne () throws IOException { User user = userDao.findById(52 ); System.out.println(user); } @Test public void testFindByName () throws IOException { List<User> users = userDao.findByName("%王%" ); for (User user : users){ System.out.println(user); } }
5.2.4 返回一行一列和占位符分析 IUserDao.java
IUserDao.xml
1 2 3 4 <select id ="findTotal" resultType ="int" > select count(id) from user; </select >
MybatisTest.java
1 2 3 4 5 6 7 8 @Test public void testFindTotal () throws IOException { int count = userDao.findTotal(); System.out.println(count); }
占位符的小细节
5.2.5 获取保存数据后的id 新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相
当于我们要在新增后将自动增长auto_increment
的值返回。
IUserDao.xml
1 2 3 4 5 6 7 8 9 10 11 <insert id ="saveUser" parameterType ="com.hongyi.domain.User" > <selectKey keyProperty ="id" keyColumn ="id" resultType ="int" order ="AFTER" > select last_insert_id(); </selectKey > insert into user(username,address,sex,birthday) values (#{username},#{address},#{sex},#{birthday}); </insert >
MybatisTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testSave () throws IOException { User user = new User (); user.setUsername("曾弘毅" ); user.setAddress("中国" ); user.setSex("男" ); user.setBirthday(new Date ()); System.out.println("保存操作之前:" +user); userDao.saveUser(user); System.out.println("保存操作之后:" +user); }
测试结果:
5.3 Mybatis参数深入 5.3.1 parameterType
参数配置 SQL 语句传参,使用标签的 parameterType
属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,即传递pojo包装对象。
OGNL
表达式
OGNL:Object Graphic Navigation Language,它是通过对象的取值方法来获取数据,在写法上把get给省略了。例如我们在获取用户的名称:
mybatis中为什么能直接写username
,而不用user.
呢?因为在parameterType
中已经提供了属性所属的类,此时不需要再写对象名。例如:
1 2 3 4 <update id ="updateUser" parameterType ="com.hongyi.domain.User" > update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update >
例如这里的#{username}
,#{address}
等属性,ognl表达式会认为是com.hongyi.domain.User类的属性字段。
传递pojo包装对象
QueryVo.java
1 2 3 4 5 6 7 8 9 10 11 public class QueryVo { private User user; public User getUser () { return user; } public void setUser (User user) { this .user = user; } }
IUserDao.java
1 2 3 4 5 6 List<User> findUserByVo (QueryVo vo) ;
IUserDao.xml
1 2 3 4 5 <select id ="findUserByVo" parameterType ="com.hongyi.domain.QueryVo" resultType ="com.hongyi.domain.User" > select * from user where username like #{user.username}; </select >
MybatisTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testFindByVo () throws IOException { QueryVo queryVo = new QueryVo (); User user = new User (); user.setUsername("%王%" ); queryVo.setUser(user); List<User> users = userDao.findUserByVo(queryVo); for (User u : users){ System.out.println(u); } }
5.3.2 输出结果封装 resultType
属性可以指定结果集的类型,它支持基本类型和实体类类型。需要注意的是,它和 parameterType
一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名。同时,当是实体类名称时,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
解决实体类属性和数据库列名不一致的两种方法
实际开发中,Java的命名规范通常采用驼峰法,而数据库字段的名称通常采用下划线的方式,因此会造成两者的不一致。例如:
User.java
类的属性:
1 2 3 4 5 private Integer userId;private String userName;private String userAddress;private String userSex;private Date userBirthday;
数据库中表的字段名:
1 2 3 4 5 <select id ="findAll" resultType ="com.hongyi.domain.User" > select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user; </select >
方法二:在映射配置文件IUserDao.xml
中配置resultMap
标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <resultMap id ="userMap" type ="com.hongyi.domain.User" > <id property ="userId" column ="id" > </id > <result property ="userName" column ="username" > </result > <result property ="userSex" column ="sex" > </result > <result property ="userAddress" column ="address" > </result > <result property ="userBirthday" column ="birthday" > </result > </resultMap > <select id ="findAll" resultMap ="userMap" > select * from user; </select >
6 配置文件 6.1 配置内容和顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -properties(属性) --property -settings(全局配置参数) --setting -typeAliases(类型别名) --typeAliase --package -typeHandlers(类型处理器) -objectFactory(对象工厂) -plugins(插件) -environments(环境集合属性对象) --environment(环境子属性对象) ---transactionManager(事务管理) ---dataSource(数据源) -mappers(映射器) --mapper --package
6.2 properties 在使用 properties
标签配置时,我们可以采用两种方式指定属性配置。
1 2 3 4 5 6 <properties > <property name ="driver" value ="xxxx" /> <property name ="url" value ="xxxx" /> <property name ="username" value ="xxxx" /> <property name ="password" value ="xxxx" /> </properties >
使用${}
来引用属性:
1 2 3 4 5 6 <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 >
方法2:新建jdbc.properties
文件,然后使用标签引用
1 <properties resource ="jdbc.properties" />
1 2 3 4 jdbc.driver =com.mysql.cj.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/test?characterEncoding=utf8 jdbc.username =root jdbc.password =12345678
6.3 typeAliases 如果没有设置别名,则在映射文件中,必须使用全限定类名:
1 2 3 <select id ="getUserById" resultType ="com.hongyi.pojo.User" > select * from t_user where id = 1 </select >
设置别名有两种方法:
1 2 3 4 <typeAliases > <typeAlias alias ="User" type ="com.hongyi.pojo.User" > </typeAlias > </typeAliases >
此时可以使用别名:
1 2 3 <select id ="getUserById" resultType ="User" > select * from t_user where id = 1 </select >
1 2 3 4 <typeAliases > <package name ="com.hongyi.pojo" /> </typeAliases >
1 2 3 4 5 6 7 8 9 <select id ="getUserById" resultType ="User" > select * from t_user where id = 1 </select > <select id ="getUserById" resultType ="user" > select * from t_user where id = 1 </select >
6.4 mappers 有3种方法来配置xml
映射文件。
1 <mapper resource ="com/hongyi/dao/IUserDao.xml" />
1 <mapper class ="com.hongyi.dao.UserDao" />
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一级目录中。
1 <package name ="com.hongyi.mybatis.mapper" />
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一级目录中。
7 Mybatis连接池 7.1 连接池分类 Mybatis 将它自己的数据源分为三类:
UNPOOLED
:不使用连接池的数据源
POOLED
:使用连接池的数据源
JNDI
:使用 JNDI 实现的数据源
相应地,MyBatis 内部分别定义了实现了java.sql.DataSource
接口的 UnpooledDataSource
,PooledDataSource
类来表示 UNPOOLED、POOLED 类型的数据源。
在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)。
7.2 连接池配置 我们的数据源配置就是在 SqlMapConfig.xml
文件中,具体配置如下:
1 2 3 4 5 6 7 8 <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost/sakila" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource >
7.3 Unpooled配置分析 接口DataSource
中有一个重要方法getConnection
,即获取连接:
1 2 3 4 5 6 public interface DataSource extends CommonDataSource , Wrapper { Connection getConnection () throws SQLException; }
1 2 3 public Connection getConnection () throws SQLException { return this .doGetConnection(this .username, this .password); }
调用了doGetConnection
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private Connection doGetConnection (String username, String password) throws SQLException { Properties props = new Properties (); if (this .driverProperties != null ) { props.putAll(this .driverProperties); } if (username != null ) { props.setProperty("user" , username); } if (password != null ) { props.setProperty("password" , password); } return this .doGetConnection(props); }
调用doGetConnection(props)
:
1 2 3 4 5 6 7 8 9 10 private Connection doGetConnection (Properties properties) throws SQLException { this .initializeDriver(); Connection connection = DriverManager.getConnection(this .url, properties); this .configureConnection(connection); return connection; }
initializeDriver
注册驱动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private synchronized void initializeDriver () throws SQLException { if (!registeredDrivers.containsKey(this .driver)) { try { Class driverType; if (this .driverClassLoader != null ) { driverType = Class.forName(this .driver, true , this .driverClassLoader); } else { driverType = Resources.classForName(this .driver); } } catch (Exception var3) { throw new SQLException ("Error setting driver on UnpooledDataSource. Cause: " + var3); } } }
7.4 Pooled配置分析 PooledDataSource
类同样实现了DataSource
接口和对应方法。
成员变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class PooledDataSource implements DataSource { private static final Log log = LogFactory.getLog(PooledDataSource.class); private final PoolState state = new PoolState (this ); private final UnpooledDataSource dataSource; protected int poolMaximumActiveConnections = 10 ; protected int poolMaximumIdleConnections = 5 ; protected int poolMaximumCheckoutTime = 20000 ; protected int poolTimeToWait = 20000 ; protected int poolMaximumLocalBadConnectionTolerance = 3 ; protected String poolPingQuery = "NO PING QUERY SET" ; protected boolean poolPingEnabled; protected int poolPingConnectionsNotUsedFor; private int expectedConnectionTypeCode; }
空参构造器:
1 2 3 public PooledDataSource () { this .dataSource = new UnpooledDataSource (); }
PoolState
类记录了连接池的状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class PoolState { protected PooledDataSource dataSource; protected final List<PooledConnection> idleConnections = new ArrayList (); protected final List<PooledConnection> activeConnections = new ArrayList (); protected long requestCount = 0L ; protected long accumulatedRequestTime = 0L ; protected long accumulatedCheckoutTime = 0L ; protected long claimedOverdueConnectionCount = 0L ; protected long accumulatedCheckoutTimeOfOverdueConnections = 0L ; protected long accumulatedWaitTime = 0L ; protected long hadToWaitCount = 0L ; protected long badConnectionCount = 0L ; }
PooledDataSource
类中getConnection
实现:
1 2 3 4 5 6 7 public Connection getConnection () throws SQLException { return this .popConnection(this .dataSource.getUsername(), this .dataSource.getPassword()).getProxyConnection(); } public Connection getConnection (String username, String password) throws SQLException { return this .popConnection(username, password).getProxyConnection(); }
调用了popConnection
方法,即从连接池中取出(pop
)一个连接,并且是同步地取。
大致流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 private PooledConnection popConnection (String username, String password) throws SQLException { boolean countedWait = false ; PooledConnection conn = null ; long t = System.currentTimeMillis(); int localBadConnectionCount = 0 ; while (conn == null ) { synchronized (this .state) { PoolState var10000; if (!this .state.idleConnections.isEmpty()) { conn = (PooledConnection)this .state.idleConnections.remove(0 ); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool." ); } } else if (this .state.activeConnections.size() < this .poolMaximumActiveConnections) { conn = new PooledConnection (this .dataSource.getConnection(), this ); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "." ); } } else { PooledConnection oldestActiveConnection = (PooledConnection)this .state.activeConnections.get(0 ); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > (long )this .poolMaximumCheckoutTime) { ++this .state.claimedOverdueConnectionCount; var10000 = this .state; var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; var10000 = this .state; var10000.accumulatedCheckoutTime += longestCheckoutTime; this .state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException var15) { log.debug("Bad connection. Could not roll back" ); } } conn = new PooledConnection (oldestActiveConnection.getRealConnection(), this ); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "." ); } } else { try { if (!countedWait) { ++this .state.hadToWaitCount; countedWait = true ; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + this .poolTimeToWait + " milliseconds for connection." ); } long wt = System.currentTimeMillis(); this .state.wait((long )this .poolTimeToWait); var10000 = this .state; var10000.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException var16) { break ; } } } if (conn != null ) { if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(this .assembleConnectionTypeCode(this .dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); this .state.activeConnections.add(conn); ++this .state.requestCount; var10000 = this .state; var10000.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection." ); } ++this .state.badConnectionCount; ++localBadConnectionCount; conn = null ; if (localBadConnectionCount > this .poolMaximumIdleConnections + this .poolMaximumLocalBadConnectionTolerance) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database." ); } throw new SQLException ("PooledDataSource: Could not get a good connection to the database." ); } } } } } if (conn == null ) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection." ); } throw new SQLException ("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection." ); } else { return conn; } }
8 Mybatis事务 8.1 JDBC中的事务 在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()
方法就可以调整。
8.2 事务提交方式 Mybatis事务的提交本质上使用了JDBC中的connection.commit()
。
Mybatis中事务的提交方式,本质上调用 JDBC 的 setAutoCommit()
来实现事务控制。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Test public void testSaveUser () throws Exception { User user = new User (); user.setUsername("mybatis user09" ); int res = userDao.saveUser(user); System.out.println(res); System.out.println(user.getId()); } @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder (); factory = builder.build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception{ session.commit(); session.close(); in.close(); }
执行结果:
这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CRUD 操作过程中,我们都要手动进行事务的提交,原因是 setAutoCommit()
方法,在执行时它的值被设置为 false 了,所以我们在 CRUD 操作中,必须通过 sqlSession.commit()
方法来执行提交操作。
8.3 自动提交事务 openSession
有一个重载的构造函数,传入布尔值来决定是否开启自动提交,默认为false
。
1 2 3 4 public interface SqlSessionFactory { SqlSession openSession (boolean var1) ; }
实现类:
1 2 3 4 5 6 public class DefaultSqlSessionFactory implements SqlSessionFactory { public SqlSession openSession () { return this .openSessionFromDataSource(this .configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null , false ); } }
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder (); factory = builder.build(in); session = factory.openSession(true ); userDao = session.getMapper(IUserDao.class); }
执行结果:
9 Mybatis注解开发 9.1 简介 Mybatis支持注解开发,但是需要明确的是,Mybatis仅仅是把 映射配置文件(IxxxDao.xml) 使用注解代替了,而Mybatis的全局配置文件仍然是xml
。
常用注解:
@Select
:用于配置查询语句。相当于select标签
@Insert
:用于配置插入语句。相当于insert标签
@SelectKey
:用于插入数据时,获取主键值
@Update
@Delete
@Results
:相当于resultMap
标签
@Result
:相当于resultMap
里的id
或者result
标签
@Many
:相当于resultMap
里的collection
@One
:相当于resultMap
里的association
配置文件:
1 2 3 <mappers > <package name ="com.hongyi.dao" /> </mappers >
9.2 CRUD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public interface IUserDao { @Select("select * from user") List<User> findAll () ; @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})") void saveUser (User user) ; @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}") void updateUser (User user) ; @Delete("delete from user where id=#{id} ") void deleteUser (Integer userId) ; @Select("select * from user where id=#{id} ") User findById (Integer userId) ; @Select("select * from user where username like '%${value}%' ") List<User> findUserByName (String username) ; @Select("select count(*) from user ") int findTotalUser () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception{ in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder ().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception{ session.commit(); session.close(); in.close(); } @Test public void testSave () { User user = new User (); user.setUsername("mybatis annotation" ); user.setAddress("北京市昌平区" ); userDao.saveUser(user); } @Test public void testUpdate () { User user = new User (); user.setId(57 ); user.setUsername("mybatis annotation update" ); user.setAddress("北京市海淀区" ); user.setSex("男" ); user.setBirthday(new Date ()); userDao.updateUser(user); } @Test public void testDelete () { userDao.deleteUser(51 ); } @Test public void testFindOne () { User user = userDao.findById(57 ); System.out.println(user); } @Test public void testFindByName () { List<User> users = userDao.findUserByName("mybatis" ); for (User user : users){ System.out.println(user); } } @Test public void testFindTotal () { int total = userDao.findTotalUser(); System.out.println(total); } }
9.3 多表查询 略