开篇明意,TEXT系列不能设置默认值。这个是我遇到最恶心的地方。

TEXT和BLOB字段作为mysql中较为奇葩,用的也不算多的字段确实是很神奇的存在。

类型

先说说有哪几个类型吧。BLOB分为TINYBLOB,BLOB,MEDIUMBLOBL,LONGBLOB。同样的TEXT字段分为TINYTEXT,TEXT,MEDIUMTEXT,LONGTEXT。TEXT与BLOB对应的存储大小是一样的,不过BLOB字段所存储的value是被作为二进制串进行处理,不涉及到字符集等等,在比较时也是作为二进制串进行 比较。TEXT字段则不同,作为字符串被处理。

存储

在存储的时候BLOB与TEXT不与他们对应的记录存在同一行里,而是新建一个对象存储,在改行纪录里面只存储指向这个纪录的“指针”

The internal representation of a table has a maximum row size of 65,535 bytes, even if the storage engine is capable of supporting larger rows. This figure excludes BLOB or TEXT columns, which contribute only 9 to 12 bytes toward this size. For BLOB and TEXT data, the information is stored internally in a different area of memory than the row buffer.

看到这里时为什么不能够设置默认值似乎就已经得到了答案。在给TEXT和BLOB设置默认值的时候必然会申请一块新的空间来保存“默认值”,这样看来允许设置默认值而大量浪费空间的做法就非常不划算。所以保护存储空间应该是不允许设默认值的第一个考虑。

在存储TEXT和BLOB字段的时候会进行截断,多于可存储范围的数据将会被截断并且抛出一个warn,如果被截断的是TEXT字段并且在截断处是连续的字符而不是空格的时候会抛出一个error。这个不是我们乐意看到的,我们最好在存储这两种字段的时候对存储的内容进行一个检测,是不是会超出存储极限。

索引

mysql索引大法好,什么字段都能无差别加索引,不过给这货加索引还是要注意点。首先要给这货明确要比较的前缀长度。

When you index a BLOB or TEXT column, you must specify a prefix length for the index.

这么长~的字段不指定比较前缀长度,你确定不是要玩死mysql索引?对于前缀长度内的字段,空格也被算在比较范围内。例如:"mysql" 与 "mysql "是不同的。如果你的索引是唯一索引,这个是很需要注意的~

读取

在读取这个字段的时候,如果你不确定一定需要,请不要去读这个字段。为啥呢?

Instances of BLOB or TEXT columns in the result of a query that is processed using a temporary table causes the server to use a table on disk rather than in memory because the MEMORY storage engine does not support those data types

原来,每次读取这个字段的时候都要生成一张在硬盘里面的临时表。这就是第二个不让设置默认值的原因。因为习惯上往往喜欢用select * from XXX这样的做法,如果没有注意到这个字段导致读取时的区别,可能会因为这一个字段的短短的默认值去硬盘里面建临时表并读取。

这下看到不给TEXT和BLOB字段设置默认值,没脾气了。原来你爱我爱的这么深沉。

我的人生分为两个阶段,没用mybatisGenerator的阶段和用了的阶段。

因为这个简直好用到死啊。。。这个数据库逆向框架代码生成工具就是为我这种懒人准备的啊。

进入正题,怎么用呢?用着方便,配置起来也非常简单。

最关键的文件就是generator.xml这个文件是用于指定数据库jar包位置,数据库连接信息,生成包的位置,表名等信息。是指导mybatis-generator-core这个jar包的生成工具。 先贴一份xml,再慢慢道来。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
  <properties url="file:///Users/test/dev/testproject/src/main/webapp/WEB-INF/conf/database.properties"/>
  
  <classPathEntry
        location="/Users/test/.m2/repository/mysql/mysql-connector-java/5.1.15/mysql-connector-java-5.1.15.jar" />
   
  <!-- 一个数据库一个context -->
  <context id="waimai" targetRuntime="MyBatis3">
  	<!-- 引入配置文件 -->  
  	
  	<!-- 注释 -->  
    <commentGenerator >  
        <property name="suppressAllComments" value="true"/>	<!-- 是否取消注释 -->  
        <property name="suppressDate" value="true" /> 			<!-- 是否取消注释时间戳-->  
    </commentGenerator> 
  	
	<!-- jdbc连接 -->  
    <jdbcConnection 
    	driverClass="${database.driverClassName}"  
        connectionURL="${database.url}" 
        userId="${database.username}"  
        password="${database.password}" 
    />  
      
    <!-- 类型转换 -->  
    <javaTypeResolver>  
        <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->  
        <property name="forceBigDecimals" value="false"/>  
    </javaTypeResolver>  
      
    <!-- 生成实体类 -->    
    <javaModelGenerator 
    	targetPackage="com.test.domain"  
        targetProject="testproject/src/main/java" >
        <!-- 是否在当前路径下新加一层schema, eg:false:com.oop.eksp.user.model,true:com.oop.eksp.user.model.[schemaName] -->  
        <property name="enableSubPackages" value="false"/>  
        <!-- 是否针对string类型的字段在set的时候进行trim调用 -->  
        <property name="trimStrings" value="true"/>  
    </javaModelGenerator>  
      
    <!-- 生成map xml文件 -->  
   <sqlMapGenerator targetPackage="mybatis"
                    targetProject="testproject/src/main/resources" >
		<!--是否在当前路径下新加一层schema,eg:false:com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName]  --> 
        <property name="enableSubPackages" value="false" />  
    </sqlMapGenerator>
      
    <!-- 生成map xml对应client,也就是接口dao -->      
    <javaClientGenerator targetPackage="com.test.dao"  
        targetProject="testproject/src/main/java" type="XMLMAPPER" >
		<!--是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
        <property name="enableSubPackages" value="false" />  
    </javaClientGenerator> 
     
    <!-- 配置表信息 -->      
    <!-- tableName为对应的数据库表 domainObjectName是要生成的实体类 enable*ByExample是否生成example类   -->  
    <table tableName="test"   domainObjectName="Test"
    	   enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false">  
        <!--忽略列,不生成该字段 -->  
        <ignoreColumn column="test_column" />   
       	<!-- 指定列的java数据类型   -->
	<columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />  
     </table>    
  </context>
</generatorConfiguration>

其实上面的代码里面已经有一些注释了,大家应该也能看明白,我就简单的讲一下每个配置是做什么的。

  • properties 可以用来存放一些配置信息,这样可以不在这个config文件里面存放数据库的信息
  • classPathEntr 用于指明进行数据库连接所需要使用的jar包的地址,按照jar包的位置填写就可以
  • jdbcConnection 用于指明数据库的配置信息,我在上面xml的配置里是使用的property里的信息,所有不用写出来
  • javaTypeResolver 进行类型转换的一些信息forceBigDecimals为false则JDBC DECIMAL、NUMERIC类型解析为Integer,若为true则解析为java.math.BigDecimal,一般我们都会把这个设为false
  • javaModelGenerator 生成的数据表对应的类存放的位置以及一些转换信息enableSubPackages意思是是不是要在你给定的路径下再生成一级对象名的文件夹一般会设为false。 trimStrings意思是是不是要把数据库里返回的数据trim一下去除空格。
  • sqlMapGenerator 生成的mapper文件存放的位置信息等,与上一个类似
  • javaClientGenerator 生成的DAO文件的存放位置信息等,与上一个类似
  • table 这个是需要转换的表的信息,enableDeleteByExample这一类的意思是是不是要生成例子,如果需要可以设置为true。ignoreColumn是需要忽略掉的列名,这一列不要出现在生成的文件中。columnOverride意思是无论数据库字段是何类型,生成的类属性都是varchar

以上就是最有用的配置信息,有了这些就可以简单的生成代码了。

生成只要一行代码

java -jar mybatis-generator-core-1.3.2.jar -configfile testproject/src/main/resources/generatorConfig.xml -overwrite
//根据自己的配置情况来更改信息,大概就是这么个意思。

现在去检查下,是不是你指定的文件夹里面有所有的需要的java文件?

巴适

ps:真是美好的一天啊

看够了zsh的老主题robbyrussell,换个亮眼的powerline主题享受下。

自从用了zsh觉得terminal原来可以如此美好。iTerm的强大功能也让我欲罢不能啊。加上PowerLine这么酷炫的主题,简直爽翻。先上图。

颜色清爽,还有git的分支图案。是不是蛮爽。

跟我一块一步一步配置下吧。

首先你当然要安装zsh和iTerm了。这个就不多说了。

  • 下载主题安装
git clone git://github.com/jeremyFreeAgent/oh-my-zsh-powerline-theme ~/.ohmyzsh-powerline
cd ~/.ohmyzsh-powerline
./install_in_omz.sh
  • 因为powerline主题用到了一些特殊的符号所以需要下载一个支持powerline的字体,如果你喜欢mac命令行自带的字体那么我推荐下载Monaco for Powerline字体。双击下载下来的字体文件点击安装字体就可以了

  • 给zsh配置主题

vim ~/.zshrc 
找到ZSH_THEME修改为powerline
保存后
source ~/.zshrc
  • 打开iTerm,把字体改为如下所示即可

接下来好好享受powerline的酷炫吧。哈哈

复杂对象,小心对待

之前看《effective java》时看到过有一原则“避免创建不必要的对象”。

在工作中也遇到过这样的问题,对于比较大的对象,创建往往开销很大,不为了一点简单的需求,创建大量的对象更是需要竭力去避免的。

首先声明,这不是说避免创建对象,创建小对象的开销很小,并不值得我们为了一丁点性能的提升而加大程序的复杂程度,这样反而容易导致错误的发生,会得不偿失。

我在工作中解决一个问题时,曾经发现过类似这创建了大量复杂对象导致的性能问题。问题大致是这样的,在技术升级过程中,为了适配老对象和新对象的匹配未提,提供了一个老对象对新对象的转换函数。这个函数很简单就是使用反射的办法操作新老对象依次复制所有的属性内容,以及转化为新的属性。看似一个没有问题的转换函数造成了性能的巨大损失。

具体的问题是:前端的需求是需要从后端拿到昨天一个很大区域的所有订单并且以点的形式显示在地图上。可是后端从接到请求到返回所有数据竟然需要60秒左右的时间,只有20000左右个订单的经纬度信息,居然耗时那么久。

我开始查找问题的时候首先估计是不是表上哪个字段没加索引,看了下表结构也没有发现问题。接着就自己试着运行了一下那段查找的sql发现返回也只有400ms左右,与60s差别太大,数据库肯定不可能是瓶颈。查了查代码才发现,原来技术改进还没搞完,有些新方法还没有提供,为了适配新对象,从老数据库里取出来的老对象都要进行转换。小对象就算了,不会有什么问题,关键是订单这个对象有70多个字段,方法更是不计其数。算下来一个对象大小得有200K左右。新建20000个200K的复杂对象,这个开销就不得了了。权衡了一下,因为前台只需要4个字段,就把新老对象转换的代码删除掉,换上个vo先用,作为临时性的方案。算是暂时解决了这个巨慢的问题。

这个工作中的小插曲算是一个提醒,以后码代码在操作复杂对象的时候一定要对可能造成的性能问题的地方有个预估。避免这样的情况再次发生。

上周工作中遇到一个问题,需要上传大文件来满足一个需求。大文件占内存啊,怎么搞呢?流式上传是一个颇为不错的解决方案。

先说说常用的两种文件上传方式:

  1. 使用CommonsMultipartFile中的transferTo方法来保存文件
  2. 使用CommonsMultipartResolver然后使用MultipartFile来接这个文件

先上个代码

@RequestMapping(value = "fileUpload", method = RequestMethod.POST) 
@ResponseBody 
public Object fileUpload(@RequestParam("fileUpload") CommonsMultipartFile file) {  
        if (!file.isEmpty()) {  
        String path = "root" + file.getOriginalFilename();  
        File localFile = new File(path);  
        try {  
            file.transferTo(localFile);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } 
    }  
    return new sRespones("success");  
} 

@RequestMapping(value = "fileup", method = RequestMethod.POST)
@ResponseBody   
public String fileUp(HttpServletRequest request)  throws Exception{  
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
        if (multipartResolver.isMultipart(request)) {  
            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;  
            Iterator<String> iter = multiRequest.getFileNames();  
            while (iter.hasNext()) {  
            MultipartFile file = multiRequest.getFile(iter.next());  
            if (file != null) {  
                String fileName = "up" + file.getOriginalFilename();  
                String path = root + fileName;  
                File localFile = new File(path);  
                file.transferTo(localFile);  
            }  
            }  
       }  
    return new sRespones("success");
}

上面两段代码就是常用的两种方式。 但是仔细分析一下transferTo()这个函数就会发现问题,transferTo()函数调用write()函数而这个函数要求上传文件已经要在内存中,或者是在磁盘里,才能成功执行。

上面第二段代码里直接用了MultipartFile,从这个接口的说明里面直接copy出来一段说明

The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storages will be cleared at the end of request processing.

这段话直接说明了,这是需要存储在内存或者磁盘里面的。所以这两种上传方式不适合上传大文件。如果文件过大则容易损耗过多的内存,非常不划算。

流式上传的优势就体现出来了,省内存,但是速度会比较慢。还是上一段代码

@RequestMapping(value="/upload",  method= RequestMethod.POST)
@ResponseBody
public Object fileUpload(@RequestParam("file") CommonsMultipartFile file)
{
try {
     InputStream is = file.getInputStream();
     InputStreamReader ris = new InputStreamReader(is);
     BufferedReader br = new BufferedReader(ris);
     ///**一行行写文件的操作***///
     ///***写完了把流关上***///
     }catch(Exception e){
     e.printStackTrace();
     }
}

与之前第一种方法写的非常相似,唯一的区别不过在于不是使用transferTo()来接受文件而是使用流来接受文件,这样就可以不断的清空流的内容,保证内存的占用不会提高。