Java EE数据持久化框架 • 【第5章 MyBatis代码生成器和缓存配置】

全部章节   >>>>


本章目录

4.1 MyBatis动

5.1 配置MyBatis Generator

5.1.1 MyBatis Generator介绍

5.1.2 MyBatis Generator XML文件示例

5.1.3 MyBatis Generator XML详解

5.1.3 <'context>标签属性

5.1.3 <'property>标签

5.1.3 <'plugin>和<'commentGenerator>标签

5.1.3 <'jdbcConnection>标签

5.1.3 <'javaModelGenerator>标签

5.1.3 <'javaClientGenerator>标签

5.1.4 实践练习

5.2 运行MyBatis Generator和Example

5.2.1 编写Java代码运行MyBatis Generator

5.2.2 生成Example

5.2.3 实践练习

5.3 MyBatis缓存

5.3.1 MyBatis一级缓存

5.3.2 MyBatis二级缓存

5.3.3 使用MyBatis二级缓存

5.3.4 实践练习

5.4 MyBatis脏数据

5.4.1 MyBatis脏数据的产生

5.4.2 MyBatis脏数据的解决方案

5.4.3 实践练习

总结:


5.1 配置MyBatis Generator

5.1.1 MyBatis Generator介绍

MyBatis在很大程度上大大降低了以前繁琐的JDBC代码,并且提供了大量的标签对SQL语句的支持,但是在操作不同数据表方面,仍然存在大量重复工作。

MyBatis的开发团队提供了一个很强大的代码生成器——MyBatis Generator(MyBatis生成器,简称MBG)

MBG自动产生包含了数据库表对应的实体类、Mapper接口类、Mapper XML文件和Example对象等,这些代码文件中几乎包含了全部的单表操作方法。

示例:要在MyBatis中使用MBG,首先需要在pom.xml中添加坐标依赖,如下:


	org.mybatis.generator
	mybatis-generator-core
	1.3.7

版本号和mybatis关系不大

5.1.2 MyBatis Generator XML文件示例

在项目的src/main/resources中创建一个generator目录,在该目录下创建一个generatorConfig.xml文件,该配置文件主要针对MBG生成代码时的一系列配置,如数据库信息、代码位置配置等,该文件内容核心结构如下:

示例:

Java EE数据持久化框架 • 【第5章 MyBatis代码生成器和缓存配置】_第1张图片



    
    
        
        
        
        
        
            
            
            
            
        
        
        
        
        
            
        
        
        
        
        
        
        
        

5.1.3 MyBatis Generator XML详解

标签

在MBG XML中至少配置1个标签,该标签用于指定生成一组对象的环境,可配置多个,但是具体运行时选择其中一个。

该标签下的子标签顺序必须如下,否则报错:

property(0个或多个)。

plugin(0个或多个)。

commentGenerator(0个或1个)。

jdbcConnection(1个)。

javaTypeResolver(0个或1个)。

javaModelGenerator(1个)。

sqlMapGenerator(0个或1个)。

javaClientGenerator(0个或1个)。

table(1个或多个)。

5.1.3 <'context>标签属性

属性名

取值

作用

id

自定义

唯一确定该标签,运行时可以设置

 

defaultModelType

定义MBG如何生成实体类

 

 

conditional(默认值)

表主键只有一列,不为该字段生成单独实体类,将该字段合并到基本实体类中

flat(推荐)

只为每张表生成一个实体类,这个实体类包含表中的所有字段

hierarchical

如果表有主键,那么该模型会产生一个单独的主键实体类

targetRuntime

生成代码的运行时环境

MyBatis3

默认值

MyBatis3Simple

这种情况不会生成与Example相关的方法

5.1.3 <'property>标签

标签

标签为属性标签,它定义一个全局意义的属性及值。

属性名

取值

autoDelimitKeywords

自动给关键字添加分隔符的属性

beginningDelimiter和endingDelimiter

配置前置分隔符和后置分隔符的属性。如果设置了前置分隔符和后置分隔符,那么,当数据库的表名、字段名中出现空格或关键字时,MyBatis将为这些名称加上分隔符。

5.1.3 <'plugin>和<'commentGenerator>标签

标签

  • 标签用来定义一个插件,用于扩展或修改通过MBG生成的代码。标签可以配置0个或者多个,个数不受限制。

标签

  • 用来配置如何生成注释信息,最多可以配置一个。常用的子标签property属性如下:

	
	
	
	
	 
	

5.1.3 <'jdbcConnection>标签

 标签

  • 该标签用于指定MBG要连接的数据库信息,该标签必选,并且只能有一个。  


标签

  • 该标签的配置用来指定JDBC类型和Java类型如何转换,最多可以配置一个,一般不配置,  默认即可。

5.1.3 <'javaModelGenerator>标签

标签

  • 该标签用来控制生成的实体类,targetPackage生成实体类存放的包名,targetProject指定目标项目路径。

	

标签

  • 该标签用于配置SQL映射器Mapper.xml文件的属性,该标签可选,最多配置一个,如不配置则只产生实体类,不产生对应的Mapper.xml映射文件。

5.1.3 <'javaClientGenerator>标签

 标签

  • 该标签用于配置Java客户端生成器(Mapper接口)的属性,该标签可选,最多配置一个。如果不配置该标签,就不会生成Mapper接口。
  • 配置中的type属性,推荐使用XMLMAPPER



标签

标签用于配置需要通过映射的数据库表,只有在
标签中配置过的表,才能经过上述其他配置生成最终的代码。
标签至少要配置一个,可以配置多个。
  • 标签有一个必选属性tableName,该属性指定要生成的表名,可以使用SQL通配符匹配多个表。
    例如,要生成全部的表,可以作如下配置:
    
    例如,要生成有关customer的表,可以作如下配置:

     标签是

    的子标签

    • 用来指定自动生成主键的属性(identity字段或者sequences序列)。如果指定这个标签,MBG将在生成insert的SQL映射文件中插入一个标签。这个标签非常重要,而且只能配置一个。
    • (1)column属性:生成列的列名。
    • (2)sqlStatement属性:根据不同数据库定义,mysql则定义为MySQL。

    5.1.4 实践练习

     

    5.2 运行MyBatis Generator和Example

    MBG提供了很多种运行方式,常用的有以下几种:

    • 使用Java编写代码运行。
    • 从命令提示符运行。
    • 使用Maven Plugin运行。
    • 使用Eclipse插件运行。

    使用Java编码方式运行的好处是,generatorConfig.xml中配置的一些特殊的类(如标签中type属性配置的定制化的实现类)只要在当前项目中,或者在当前项目的classpath中,就可以直接使用。

    5.2.1 编写Java代码运行MyBatis Generator

    如何编写代码运行MyBatis Generator生成实体类、接口和Mapper映射文件

    保证项目中已经添加了mybatis-generator-core.jar依赖

    在项目的src/main/java目录下新建jack.mybatis.blog.generator包(包名可自定)

    在改包下新建Generator.java类,包含main方法(注:该类代码无需掌握)

    public class Generator {
    // targetRuntime="MyBatis3Simple", 不生成Example
    public void generateMyBatis() {
    	List warnings = new ArrayList();//该集合记录MBG执行过程中的警告信息
    	boolean overwrite = true ;	//当生成的代码重复时,覆盖原代码
    	String generatorFile = “/generator/generatorConfig.xml”;	//指定配置文件路径
    	InputStream is = Generator.class.getResourceAsStream(generatorFile);//读取MBG配置文件
    	ConfigurationParser cp = new ConfigurationParser(warnings);
    	try {
    		Configuration config = cp.parseConfiguration(is);
    		DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    		MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings)		myBatisGenerator.generate(null);//执行生成代码
    	}
    		catch (Exception e) {
    			e.printStackTrace();
    		}
    		//打印出执行过程中的警告信息,以便于修改 
    		for (String warning : warnings) {
    			System.out.println(warning);
    		}	
    	}
    	//main方法为java普通类运行入口
    	public static void main(String[] args) {
    		Generator generator = new Generator();
    		generator.generateMyBatis();
    	}
    }
    

    5.2.2 生成Example

    • 在MBG的配置文件中,如果将targetRuntime属性值设置为MyBatis3,则在生成MyBatis实体类的同时,还生成与之对应的带Example的辅助查询工具类。
    • Example类包含了增加、修改、删除,以及查询时动态构建的where子句,表中的每个非BLOB列可以被包括在where子句中,Example类可以用来生成一个几乎无限的where子句,方便测试和后期使用。
    • 比如,与实体类SysUser对应的Example类为SysUserExample,它包含了常见的CRUD等方法的代码示例。

    实现一个用户管理高级查询功能,根据输入的条件去检索用户信息。这个功能还需要支持以下四种情况:

    • 当只输入用户名关键字时,需要根据用户名关键字进行模糊查询。
    • 当只一个日期时,需要获取创建时间在该日期(含)之后的用户信息。
    • 当同时输入用户名关键字和日期时,用这两个条件去查询匹配的用户。
    • 如果用户名关键字和日期都没有输入,则查询全部用户信息。

     通过MGB已逆向生成了所有表所对应的实体类、Mapper接口,并且还生成Example类,现在只需定义一个测试方法来实现上述需求,代码如下:

    SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
    SysUserExample userExample = new SysUserExample();	// 创建Example对象
    SysUserExample.Criteria userCriteria = userExample.createCriteria();	// 创建查询条件
    	if(!keyUserName.isEmpty())
    		// 设置根据用户名关键字检索的查询条件
    		userCriteria.andUserNameLike('%'+keyUserName+'%');
    	if(!createTimeStr.isEmpty()) {
    		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    		Date createTime = formatter.parse(createTimeStr);
    	                // 设置根据时间检索的查询条件
    		userCriteria.andCreateTimeGreaterThanOrEqualTo(createTime);
    	}
    	 // 执行查询
    	List users = userMapper.selectByExample(userExample);
    	System.out.println("用户数量:"+users.size());
    
    

    updateByExample()方法和updateByExampleSelective()方法都可以实现更新,区别是当对象的属性为空时,第一个方法会将值更新为null,第二个方法不会更新null属性的字段。

    更新id在3至7范围的用户的密码为999999,采用UserMapper的updateByExampleSelective()方法实现以上需求:

    	SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
    	SysUserExample userExample = new SysUserExample();	
    	SysUserExample.Criteria userCriteria = userExample.createCriteria();
    	// 更新条件:用户id在3~7的范围
    	userCriteria.andIdBetween(3L, 7L);
    	// 创建一个要设置的对象
    	SysUser user = new SysUser();
    	// 将密码设置为999999 
    	user.setUserPassword("999999");
    	// 执行查询
    	int result = userMapper.updateByExampleSelective(user, userExample);
    	sqlSession.commit();
    	System.out.println("更新了"+result+"个用户");
    

    相信大家对Example应该己经有所了解,使用Example查询能解决大部分复杂的单表操作,从一定程度上能减少工作量。但是建议在条件很多并且判断很多的情况下,避免使用Example查询,因为生成的代码毕竟不是特别人性化,在这种情况下,根据需要自己去完善Mapper XML方式会更有效。

    5.2.3 实践练习

     

    5.3 MyBatis缓存

    使用缓存可以使应用更快地获取数据,避免频繁的数据库交互,尤其是在查询越多、缓存命中率越高的情况下,使用缓存的作用就越明显。

    缓存的应用在企业级开发中随处可见,合理的使用缓存能大大提高项目的性能,因为缓存的数据一般是在内存中,可以降低对数据库的访问次数。

    MyBatis作为持久化框架,提供了非常强大的查询缓存特性,可以非常方便地配置和定制使用。MyBatis提供了一级缓存和二级缓存,其中一级缓存默认启用,不能控制,所以一般提到的缓存,是指二级缓存(可配置)。

    5.3.1 MyBatis一级缓存

    一级缓存是MyBatis默认开启且无法控制的缓存,是属于SqlSession级别,目的是提高查询结果的利用率,在同一个SqlSession实例下进行查询,会使用到一级缓存,而不同的SqlSession实例则不会使用。

    	SqlSession sqlSession = getSqlSession();
    	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);	
    	// 查询id为1L的用户
    	SysUser user1 = userMapper.selectUserById(1L);
    	// 更新之前userName的值为admin
    	System.out.println("更新之前,user1的userName="+user1.getUserName());
    	// 更新user1的userName
    	user1.setUserName("new_admin");
    	// 再次获取id为1L的用户
    	 SysUser user2 = userMapper.selectUserById(1L);
    	// 虽然没有更新数据库,但是user2的userName的值和user1重新赋值的名字相同,
    	// 它们都是new_admin
    	System.out.println("user2的userName="+user2.getUserName());
    	// 由于从一级缓存中存在键值selectUserById(1L),因此user1和user2是同一个实例
    	System.out.println("user1==user2:"+(user1==user2));
    

    Java EE数据持久化框架 • 【第5章 MyBatis代码生成器和缓存配置】_第2张图片

    示例:

    重新开启一个新的SqlSession实例,再次查询该数据:

    	System.out.println("开启新的SqlSession");
    	sqlSession = getSqlSession(); 	// 开启一个新的SqlSession
    	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);	
    	// 查询id为1L的用户
    	SysUser user2 = userMapper.selectUserById(1L);
    	// 第二个SqlSession获取的的userName的值是admin
    	System.out.println("user2的userName="+user2.getUserName());
    	// 这里的user2和前一个SqlSession查询的结果是两个不同的实例
    	System.out.println("user1==user2:"+(user1==user2));		
    	// 执行删除操作(或者新增、更新操作),将清空一级缓存
    	userMapper.deleteUserById(10L);
    	sqlSession.commit();
    	// 再次查询id为1L的用户
    	SysUser user3 = userMapper.selectUserById(1L);
    	System.out.println("user3的userName="+user3.getUserName());
    	// 由于将清空了一级缓存,因此user2和user3不是同一个实例
    	System.out.println("user2==user3:"+(user2==user3));
    
    

    User2和刚才的user1虽然数据一样,但是不是同一个对象,打印false

    执行删除操作后,一级缓存会被清除,所以查询的user3和user2也不是同一个对象,打印false

    1)在同一个SqlSession下进行的查询操作,查询的结果会默认放入一级缓存中,再次查询时会到一级缓存中去寻找是否存在该数据,如果存在则取出,而不查询数据库;如果不存在,则再去查找数据库,并且放入一级缓存中。当然在SqlSession下进行任何的新增、更新和删除操作都会清空一级缓存,以保证数据的准确性。

    2)如果希望某个查询操作不使用一级缓存,则可以在配置时进行如下设置:

    
    
    

    配置为true后,会在查询数据前清空当前的一级缓存,一般不建议设置

    5.3.2 MyBatis二级缓存

    MyBatis二级缓存是Mapper级别的缓存,每一个Mapper都有一个二级缓存区域(按namespace区分)

    如果两个Mapper的namespace相同,则这两个Mapper执行SQL查询所得到的数据将存在相同的二级缓存区域中,即多个SqlSession可能共用二级缓存。

    MyBatis的全局配置settings中有一个参数cacheEnabled,该参数是二级缓存的全局开关,在mybatis核心主配置中可以添加配置,如下:

    
    
    	
    
    

    在保证二级缓存的全局配置开启的情况下,不同Mapper下的二级缓存需要设置开启,有两种不同的方式进行Mapper命名空间缓存启用方式。

    使用Mapper.xml开启二级缓存只需要在接口对应的Mapper.xml中添加元素即可,如下:

    
    
    	import org.apache.ibatis.annotations.CacheNamespace;
    
    @CacheNamespace(
    	eviction=FifoCache.class,
    	flushInterval=60000,
    	size=512,
    	readWrite=true
    )
    public interface UserMapper{
    	// 接口方法
    }
    

     二级缓存配置属性参数说明:

    属性名

    取值

    作用

     

    eviction

    (回收策略)

    LRU(默认值)

    移除最长时间不被使用的对象,这是默认值

    FIFO

    按对象进入缓存的顺序来移除它们

    SOFT

    移除基于垃圾回收器状态和软引用规则的对象

    WEAK

    更积极地移除基于垃圾收集器状态和弱引用规则的对象

    flushinterval

    (刷新间隔)

    可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新

    size(引用数目)

    可以被设置为任意正整数,默认值是1024

    readOnly(只读)

    可设置为true或false。只读的缓存所有调用者返回缓存对象的相同实例,这些对象不能被修改;可读写缓存会通过序列化返回缓存对象的拷贝,这种方式慢一些,但是安全,因此默认值是false

    默认的二级缓存会有如下效果:

    映射语句文件中的所有select语句将会被缓存。

    映射语句文件中的所有insert、update和delete语句会刷新缓存。

    缓存会使用Least Recently Used(LRU,最近最少使用)算法来收回。

    如果没有设置刷新时间间隔,缓存不会以任何时间顺序来刷新。

    默认情况下不能同时使用Mapper.xml和接口两种方式都开启二级缓存,会出现配置冲突,所以一般使用一种配置方式即可,最好不要混用。

    5.3.3 使用MyBatis二级缓存

    如对UserMapper配置二级缓存后,当调用UserMapper所有的select查询方法时,二级缓存就己经开始起作用,在使用可读写缓存时,要求缓存的实体类需要实现序列化接口,如SysUser实体类:

    public class SysUser implements Serializable{
    	// 其他属性和getter()方法、setter()方法
    }
    

     调用UserMapper中的查询方法测试缓存:

    sqlSession = getSqlSession();   // 开启一个新的SqlSession
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 第二个SqlSession获取的的userName的值是从二级缓存取得,因此是new_admin
    SysUser user2 = userMapper.selectUserById(1L); 
    // 由于二级缓存配置为读写缓存,user1和user2是两个不同的实例
    System.out.println("user1==user2:"+(user1==user2));
    SysUser user3 = userMapper.selectUserById(1L);// 这里仍然从二级缓存中取出
    // user2和user3都是2级别缓存读取出来的结果,但是为false
    System.out.println("user2==user3:"+(user2==user3));
    
    

    因为二级读写缓存是反序列化的结果,所以为false

    5.3.4 实践练习

     

    5.4 MyBatis脏数据

    5.4.1 MyBatis脏数据的产生

    二级缓存虽然能提高应用效率,减轻数据库服务器的压力,但是如果使用不当,很容易产生脏数据。

    MyBatis的二级缓存和命名空间绑定的,每个Mapper命名空间下的二级缓存互不影响,但是难免遇到一个命名空间所缓存的数据,在数据库中已被其他命名空间修改,缓存的数据则不是数据库最真实的,即脏数据。

    通过案例来演示脏数据的产生:

    示例:在UserMapper中创建了selectUserAndRoleById()方法,该方法获取指定用户id的用户信息,用户信息要求包含该用户所对应的角色信息

    
    
    
    
    

    该方法查询的数据会被缓存到UserMapper空间下的二级缓存中

    示例:在RoleMapper.xml添加二级缓存配置,增加元素,代码如下

    
    

    让SysUser类实现Serializable接口

    public class SysUser implements Serializable  {
    	//相关属性略
    }
    

    通过案例来演示脏数据的产生:

    示例:在RoleMapper中创建了selectRoleById()方法,该方法获取指定角色id的角色,另外,再创建updateRole()方法,该方法更新指定角色id的角色信息

    
    	
    	
    	
    	
    	
    	
    	
    
    	update sys_role set
    	role_name=#{roleName},enabled=#{enabled},create_by=#{createBy},
    	create_time=#{createTime,jdbcType=TIMESTAMP} where id=#{id}
    
    

    RoleMapper下更新角色的信息不会影响到UserMapper的二级缓存

    1、UserMapper空间下定义开启二级缓存,并且查询方法会把用户及角色数据缓存至二级缓存中。

    2、RoleMapper中的修改方法调用后,不会影响到UserMapper空间下的二级缓存,因为二级缓存只在各自空间下有效。

    3、所以UserMapper中二级缓存中的数据并非数据库中最新的数据,会造成数据不合理的脏数据。

    5.4.2 MyBatis脏数据的解决方案

     如何避免脏数据的出现?需要用到参照缓存了。当某几个表可以作为一个业务整体的查询来源时,通常是让几个会关联的数据表同时使用同一个二级缓存,这样就能解决脏数据问题。

    将UserMapper.xml中的缓存配置修改如下:

    
    	
    	 
    
    

    参照缓存可以解决关联较少的情况,如果有几十个表且关联复杂时,使用参照缓存意义不大。因此使用二级缓存需要满足一定的条件:

    • 以查询为主的应用中,只有尽可能少的新增、删除和更新操作。
    • 查询业务绝大多数都是对单表进行操作,由于很少存在互相关联的情况,因此不会出现脏数据。
    • 可以按业务划分对表进行分组时,如果关联的表比较少,可以通过参照缓存进行配置。

    5.4.3 实践练习

     

    总结:

    • 利用MyBatisGenerator可以快速生成针对数据库表的实体类、接口、Mapper.xml映射配置文件,以及Example辅助查询工具类。
    • 缓存是一种将数据库查询的数据进行临时存储,一般是存储在内存中,后续查询时减少和数据库的交互次数,提高程序性能的技术。
    • MyBatis中提供了一级缓存和二级缓存,其中一级缓存默认存在,不可控制,同一SqlSession范围内的操作共享该缓存,增、删、改后将清除缓存。
    • 二级缓存需要配置才能生效,属于各自命名空间范围的缓存,在同一命名空间范围内,即使不同SqlSession也可共用,使用时需要注意脏数据的产生。

    你可能感兴趣的:(Java,EE数据持久化框架笔记,数据库,mysql,mybatis)