项目相关的CVS操作

使用以下代码做成一个批处理文件,即可做到项目脱离CVS或切换CVS地址操作

1、项目脱离CVS

@echo off
echo Deleting CVS folders and files under: %1
REM Open Folder specified by parameter.
cd %1
REM Recursive delete command
for /f “tokens=“ %%i in (‘dir /b/a/s CVS‘) do @rmdir /q /s “%%i”
echo Done!

2、更改项目cvs地址和切换用户

cd 项目根路径
@echo off
for /f “delims=” %%i in (‘dir /s/b Root’) do (
for /f “delims=” %%a in (‘type “%%~fi”‘) do (
set “foo=%%a”
call,set foo=%%foo::pserver:原CVS用户@原CVS地址:原CVS端口/原CVS路径=:pserver:替换后的CVS用户@替换后
的CVS地址:替换后的端口/替换后的CVS路径%%
call,echo/%%foo%%>>”%%~fi.
)
move “%%~fi.
“ “%%~fi”
)
exitw

评论

图片转换PDF文件

今天接到客户的需求,需要将一批TIF图片转换成PDF文件上传到海关和检验检疫局。废话不多说,直接上代码

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.Utilities;
import com.lowagie.text.pdf.*;
import com.lowagie.text.pdf.codec.TiffImage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: lerry.li
 * Date: 2016-09-18
 * Time: 上午11:10
 * To change this template use File | Settings | File Templates.
 */
public class ImageToPDFUtil {

    public static String convertJpgAndTif(List<String>oldPaths, String newPath){
        // 创建一个文档对象
        Document doc = new Document();
        FileOutputStream fileOS = null;
        try {
            fileOS = new FileOutputStream(newPath);
            // 定义输出文件的位置
            PdfWriter.getInstance(doc, fileOS);
            // 开启文档
            doc.open();
            for(String path : oldPaths){
                addToDoc(doc, path);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (DocumentException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        } finally {
            if (doc != null) {
                doc.close();
            }
        }
        return "success";
    }

    public static String convertManyTiff(List<String>oldPaths, String newPath){
        // 创建一个文档对象
        Document doc = new Document();
        try {
            // 定义输出文件的位置
            PdfWriter.getInstance(doc, new FileOutputStream(newPath));
            // 开启文档
            doc.open();
            Object localObject1 = null;
            Object localObject2 = null;
            Image localImage1 = null;
            for(String temp : oldPaths){
                URL paramURL = Utilities.toURL(temp);
                try {
                    if (paramURL.getProtocol().equals("file")) {
                        localObject2 = paramURL.getFile();
                        localObject2 = Utilities
                                .unEscapeURL((String) localObject2);
                        localObject1 = new RandomAccessFileOrArray((String) localObject2);
                    } else {
                        localObject1 = new RandomAccessFileOrArray(paramURL);
                    }

                    int pageNums = TiffImage
                            .getNumberOfPages((RandomAccessFileOrArray) localObject1);
                    if (pageNums > 0) {
                        for (int i = 1; i <= pageNums; i++) {
                            localObject2 = TiffImage.getTiffImage(
                                    (RandomAccessFileOrArray) localObject1, i);
                            Image jpg = (Image) localObject2;
                            // 获得图片的高度
                            float heigth = jpg.getHeight();
                            float width = jpg.getWidth();
                            // 合理压缩,h>w,按w压缩,否则按w压缩
                            int percent = getPercent(heigth, width);

                            // 设置图片居中显示
                            jpg.setAlignment(Image.MIDDLE);
                            // 按百分比显示图片的比例
                            if (width > 1024 || heigth > 786) {
                                jpg.scalePercent(percent);
                            }
                            doc.add(jpg);
                        }
                    }
                    if (localObject1 != null)
                        ((RandomAccessFileOrArray) localObject1).close();

                }catch (Exception ex){
                    ex.printStackTrace();
                    return ex.getMessage();
                }finally {
                    try{
                        if (localObject1 != null)
                            ((RandomAccessFileOrArray) localObject1).close();
                    }catch (Exception ex){
                        ex.printStackTrace();
                    }
                }

            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (DocumentException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        } finally {
            if (doc != null) {
                try{
                    doc.close();
                }catch (Exception ex){
                    ex.printStackTrace();
                }
            }
        }
        return "success";

    }

    public static boolean convert(String oldPath, String newPath) {
        // 创建一个文档对象
        Document doc = new Document();
        try {
            // 定义输出文件的位置
            PdfWriter.getInstance(doc, new FileOutputStream(newPath));
            // 开启文档
            doc.open();
            addToDoc(doc, oldPath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (doc != null) {
                doc.close();
            }
        }
        return true;
    }
    //pdf文件合并
    public static boolean mergePdfFiles(String[] files, String newfile) {
        boolean retValue = false;
        Document document = null;
        try {
            document = new Document(new PdfReader(files[0]).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(newfile));
            document.open();
            for (int i = 0; i < files.length; i++) {
                PdfReader reader = new PdfReader(files[i]);
                int n = reader.getNumberOfPages();
                for (int j = 1; j <= n; j++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(reader, j);
                    copy.addPage(page);
                }
            }
            retValue = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            document.close();
        }
        return retValue;
    }

    private static void addToDoc(Document doc, String oldPath) throws IOException, DocumentException {
        // 创建一个文档对象
        if (!oldPath.endsWith(".tif")) {
            Image jpg = Image.getInstance(oldPath); // 原来的图片的路径
            // 获得图片的高度
            float heigth = jpg.getHeight();
            float width = jpg.getWidth();
            // 合理压缩,h>w,按w压缩,否则按w压缩
            int percent = getPercent(heigth, width);
            // 设置图片居中显示
            jpg.setAlignment(Image.MIDDLE);
            // 按百分比显示图片的比例
            if (width > 1024 || heigth > 786) {
                jpg.scalePercent(percent);
            }
            doc.add(jpg);
        } else
        // tiff多页
        {
            Object localObject1 = null;
            Object localObject2 = null;
            Image localImage1 = null;
            URL paramURL = Utilities.toURL(oldPath);
            try {
                if (paramURL.getProtocol().equals("file")) {
                    localObject2 = paramURL.getFile();
                    localObject2 = Utilities
                            .unEscapeURL((String) localObject2);
                    localObject1 = new RandomAccessFileOrArray((String) localObject2);
                } else {
                    localObject1 = new RandomAccessFileOrArray(paramURL);
                }

                int pageNums = TiffImage
                        .getNumberOfPages((RandomAccessFileOrArray) localObject1);
                if (pageNums > 0) {
                    for (int i = 1; i <= pageNums; i++) {
                        localObject2 = TiffImage.getTiffImage(
                                (RandomAccessFileOrArray) localObject1, i);
                        Image jpg = (Image) localObject2;
                        // 获得图片的高度
                        float heigth = jpg.getHeight();
                        float width = jpg.getWidth();
                        // 合理压缩,h>w,按w压缩,否则按w压缩
                        int percent = getPercent(heigth, width);

                        // 设置图片居中显示
                        jpg.setAlignment(Image.MIDDLE);
                        // 按百分比显示图片的比例
                        if (width > 1024 || heigth > 786) {
                            jpg.scalePercent(percent);
                        }
                        doc.add(jpg);
                    }
                }
                if (localObject1 != null)
                    ((RandomAccessFileOrArray) localObject1).close();

            } finally {
                if (localObject1 != null)
                    ((RandomAccessFileOrArray) localObject1).close();
            }
        }
    }

    /**
     * 第一种解决方案 在不改变图片形状的同时,判断,如果h>w,则按h压缩,否则在w>h或w=h的情况下,按宽度压缩
     *
     * @param h
     * @param w
     * @return
     */

    public static int getPercent(float h, float w) {
        int p = 0;
        float p2 = 0.0f;
        if (h > w) {
            p2 = 210 / h * 279;
        } else {
            p2 = 210 / w * 279;
        }
        p = Math.round(p2);
        return p;
    }

    /**
     * 第二种解决方案,统一按照宽度压缩 这样来的效果是,所有图片的宽度是相等的
     *
     */
    public static int getPercent2(float h, float w) {
        int p = 0;
        float p2 = 0.0f;
        p2 = 530 / w * 100;
        p = Math.round(p2);
        return p;
    }

    public static void main(String[] args) {
        List<String>list = new ArrayList<String>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = sdf.format(new Date());
            String pdfPath = "D:/jpg2pdf/" + dateStr;
        File file = new File(pdfPath);
        if(!file.exists())file.mkdir();
        pdfPath += "/" + "document4.pdf";
        list.add("D:/jpg2pdf/359860201.TIF");
        list.add("D:/jpg2pdf/359860201_1.TIF");
//        convert("D:/jpg2pdf/359860201.TIF", "D:/jpg2pdf/document4.pdf");
        convertManyTiff(list, pdfPath);
    }
}
评论

使用 Spring Data JPA 简化 JPA 开发

_ Spring Data JPA 开发指南 _

本文由浅入深地讲述了使用 Spring Data JPA 需要关注的各个方面,为读者了解和使用该框架提供了指导,可以作为 Spring Data JPA
的学习指南。

从一个简单的 JPA 示例开始

本文主要讲述 Spring Data JPA,但是为了不至于给 JPA 和 Spring 的初学者造成较大的学习曲线,我们首先从 JPA 开始,简单介绍一个
JPA 示例;接着重构该示例,并引入 Spring 框架,这两部分不会涉及过多的篇幅,如果希望能够深入学习 Spring 和
JPA,可以根据本文最后提供的参考资料进一步学习。

自 JPA 伴随 Java EE 5 发布以来,受到了各大厂商及开源社区的追捧,各种商用的和开源的 JPA
框架如雨后春笋般出现,为开发者提供了丰富的选择。它一改之前 EJB 2.x 中实体 Bean 笨重且难以使用的形象,充分吸收了在开源社区已经相对成熟的
ORM 思想。另外,它并不依赖于 EJB 容器,可以作为一个独立的持久层技术而存在。目前比较成熟的 JPA 框架主要包括 Jboss 的 Hibernate
EntityManager、Oracle 捐献给 Eclipse 社区的 EclipseLink、Apache 的 OpenJPA 等。

本 文的示例代码基于 Hibernate EntityManager 开发,但是读者几乎不用修改任何代码,便可以非常容易地切换到其他 JPA
框架,因为代码中使用到的都是 JPA 规范提供的接口 /
类,并没有使用到框架本身的私有特性。示例主要涉及七个文件,但是很清晰:业务层包含一个接口和一个实现;持久层包含一个接口、一个实现、一个实体类;另 外加上一个
JPA 配置文件和一个测试类。相关类 / 接口代码如下:

清单 1. 实体类 AccountInfo.java
1 @Entity 
2  @Table(name = "t_accountinfo") 
3  public class AccountInfo implements Serializable { 
4  private Long accountId; 
5  private Integer balance; 
6 
7  // 此处省略 getter 和 setter 方法。
8  }
清单 2. 业务层接口 UserService.java
1  public interface UserService { 
2    public AccountInfo createNewAccount(String user, String pwd, Integer init); 
3  }
清单 3. 业务层的实现类 UserServiceImpl.java
 1 public class UserServiceImpl implements UserService { 
 2 
 3  private UserDao userDao = new UserDaoImpl(); 
 4 
 5  public AccountInfo createNewAccount(String user, String pwd, Integer init){ 
 6  // 封装域对象
 7  AccountInfo accountInfo = new AccountInfo(); 
 8  UserInfo userInfo = new UserInfo(); 
 9  userInfo.setUsername(username); 
10  userInfo.setPassword(password); 
11  accountInfo.setBalance(initBalance); 
12  accountInfo.setUserInfo(userInfo); 
13  // 调用持久层,完成数据的保存
14  return userDao.save(accountInfo); 
15     } 
16  }
清单 4. 持久层接口
1  public interface UserDao { 
2      public AccountInfo save(AccountInfo accountInfo); 
3  }
清单 5. 持久层的实现类
 1 public class UserDaoImpl implements UserDao { 
 2      public AccountInfo save(AccountInfo accountInfo) { 
 3          EntityManagerFactory emf = Persistence.createEntityManagerFactory("SimplePU"); 
 4          EntityManager em = emf.createEntityManager(); 
 5          em.getTransaction().begin(); 
 6          em.persist(accountInfo); 
 7          em.getTransaction().commit(); 
 8          emf.close(); 
 9          return accountInfo; 
10      } 
11  }    
清单 6. JPA 标准配置文件 persistence.xml
 1 <?xml version="1.0" encoding="UTF-8"?> 
 2  <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0"> 
 3  <persistence-unit name="SimplePU" transaction-type="RESOURCE_LOCAL"> 
 4  <provider>org.hibernate.ejb.HibernatePersistence</provider> 
 5  <class>footmark.springdata.jpa.domain.UserInfo</class> 
 6  <class>footmark.springdata.jpa.domain.AccountInfo</class> 
 7  <properties> 
 8  <property name="hibernate.connection.driver_class"
 9  value="com.mysql.jdbc.Driver"/> 
10  <property name="hibernate.connection.url" 
11  value="jdbc:mysql://10.40.74.197:3306/zhangjp"/> 
12  <property name="hibernate.connection.username" value="root"/> 
13  <property name="hibernate.connection.password" value="root"/> 
14  <property name="hibernate.dialect"
15  value="org.hibernate.dialect.MySQL5Dialect"/> 
16  <property name="hibernate.show_sql" value="true"/> 
17  <property name="hibernate.format_sql" value="true"/> 
18  <property name="hibernate.use_sql_comments" value="false"/> 
19  <property name="hibernate.hbm2ddl.auto" value="update"/> 
20  </properties> 
21  </persistence-unit> 
22  </persistence>
清单 7. 本文使用如下的 main 方法进行开发者测试
1  public class SimpleSpringJpaDemo { 
2     public static void main(String[] args) { 
3         new UserServiceImpl().createNewAccount("ZhangJianPing", "123456", 1); 
4     } 
5  }

简述 Spring 框架对 JPA 的支持

接下来我们引入 Spring,以展示 Spring 框架对 JPA 的支持。业务层接口 UserService 保持不变,UserServiceImpl
中增加了三个注解,以让 Spring 完成依赖注入,因此不再需要使用 new 操作符创建 UserDaoImpl 对象了。同时我们还使用了 Spring
的声明式事务:

清单 8. 配置为 Spring Bean 的业务层实现
1 @Service("userService") 
2  public class UserServiceImpl implements UserService { 
3  @Autowired 
4  private UserDao userDao; 
5 
6  @Transactional 
7  public AccountInfo createNewAccount( 
8  String name, String pwd, Integer init) { …… } 
9  }

对于持久层,UserDao 接口也不需要修改,只需修改 UserDaoImpl 实现,修改后的代码如下:

清单 9. 配置为 Spring Bean 的持久层实现
 1 @Repository("userDao") 
 2  public class UserDaoImpl implements UserDao { 
 3 
 4  @PersistenceContext 
 5  private EntityManager em; 
 6 
 7  @Transactional 
 8    public Long save(AccountInfo accountInfo) { 
 9  em.persist(accountInfo); 
10  return accountInfo.getAccountId(); 
11  } 
12  }
清单 10. Spring 配置文件
 1  <?xml version="1.0" encoding="UTF-8"?> 
 2  <beans...> 
 3  <context:component-scan base-package="footmark.springdata.jpa"/> 
 4  <tx:annotation-driven transaction-manager="transactionManager"/> 
 5  <bean id="transactionManager" 
 6  class="org.springframework.orm.jpa.JpaTransactionManager"> 
 7  <property name="entityManagerFactory" ref="entityManagerFactory"/> 
 8  </bean> 
 9  <bean id="entityManagerFactory" class= 
10 "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
11     </bean> 
12  </beans>
清单 11. 改造后的基于 Spring 的开发者测试代码
1  public class SimpleSpringJpaDemo{ 
2  public static void main(String[] args){ 
3  ClassPathXmlApplicationContext ctx = 
4  new ClassPathXmlApplicationContext("spring-demo-cfg.xml"); 
5  UserDao userDao = ctx.getBean("userDao", UserDao.class); 
6  userDao.createNewAccount("ZhangJianPing", "123456", 1); 
7  } 
8  }

通过对比重构前后的代码,可以发现 Spring 对 JPA 的简化已经非常出色了,我们可以大致总结一下 Spring 框架对 JPA
提供的支持主要体现在如下几个方面:

  • 首先,它使得 JPA 配置变得更加灵活。JPA 规范要求,配置文件必须命名为 persistence.xml,并存在于类路径下的 META-INF 目录中。该文件通常包含了初始化 JPA 引擎所需的全部信息。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常灵活的配置,persistence.xml 中的信息都可以在此以属性注入的方式提供。
  • 其次,Spring 实现了部分在 EJB 容器环境下才具有的功能,比如对 @PersistenceContext、@PersistenceUnit 的容器注入支持。
  • 第三,也是最具意义的,Spring 将 EntityManager 的创建与销毁、事务管理等代码抽取出来,并由其统一管理,开发者不需要关心这些,如前面的代码所示,业务方法中只剩下操作领域对象的代码,事务管理和 EntityManager 创建、销毁的代码都不再需要开发者关心了。

更进一步:Spring Data JPA 让一切近乎完美

通过前面的分析可以看出,Spring 对 JPA 的支持已经非常强大,开发者只需关心核心业务逻辑的实现代码,无需过多关注 EntityManager
的创建、事务处理等 JPA 相关的处理,这基本上也是作为一个开发框架而言所能做到的极限了。然而,Spring 开发小组并没有止步,他们再接再厉,于最近推出了
Spring Data JPA 框架,主要针对的就是 Spring
唯一没有简化到的业务逻辑代码,至此,开发者连仅剩的实现持久层业务逻辑的工作都省了,唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data
JPA 来帮你完成!

至此,读者可能会存在一个疑问,框架怎么可能代替开发者实现业务逻辑呢?毕竟,每一个应用的持久层业务甚至
领域对象都不尽相同,框架是怎么做到的呢?其实这背后的思想并不复杂,比如,当你看到 UserDao.findUserById()
这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA
做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。

接下来我们针对前面的例子进行改造,让 Spring Data JPA 来帮助我们完成业务逻辑。在着手写代码之前,开发者需要先 下载 Spring Data JPA 的发布包(需要同时下载 Spring Data Commons 和
Spring Data JPA 两个发布包,Commons 是 Spring Data 的公共基础包),并把相关的依赖 JAR 文件加入到
CLASSPATH 中。

首先,让持久层接口 UserDao 继承 Repository
接口。该接口使用了泛型,需要为其提供两个类型:第一个为该接口处理的域对象类型,第二个为该域对象的主键类型。修改后的 UserDao 如下:

清单 12. Spring Data JPA 风格的持久层接口
public interface UserDao extends Repository<AccountInfo, Long> { 
   public AccountInfo save(AccountInfo accountInfo); 
}

然后删除 UserDaoImpl 类,因为我们前面说过,框架会为我们完成业务逻辑。最后,我们需要在 Spring 配置文件中增加如下配置,以使 Spring
识别出需要为其实现的持久层接口:

清单 13. 在 Spring 配置文件中启用扫描并自动创建代理的功能
<-- 需要在 <beans> 标签中增加对 jpa 命名空间的引用 --> 
 <jpa:repositories base-package="footmark.springdata.jpa.dao"
 entity-manager-factory-ref="entityManagerFactory" 
 transaction-manager-ref="transactionManager"/>

至此便大功告成了!执行一下测试代码,然后看一下数据库,新的数据已经如我们预期的添加到表中了。如果要再增加新的持久层业务,比如希望查询出给 ID 的
AccountInfo 对象,该怎么办呢?很简单,在 UserDao 接口中增加一行代码即可:

清单 14. 修改后的持久层接口,增加一个方法声明
1 public interface UserDao extends Repository<AccountInfo, Long> { 
2 
3  public AccountInfo save(AccountInfo accountInfo); 
4 
5  // 你需要做的,仅仅是新增如下一行方法声明
6  public AccountInfo findByAccountId(Long accountId); 
7  }

下面总结一下使用 Spring Data JPA 进行持久层开发大致需要的三个步骤:

  1. 声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
  2. 在接口中声明需要的业务方法。Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。
  3. 在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 jpa:repositories 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

此 外,jpa:repository 还提供了一些属性和子标签,便于做更细粒度的控制。可以在 jpa:repository 内部使用
context:include-filtercontext:exclude-filter 来过滤掉一些不希望被扫描到的接口。具体的使用方法见
Spring
参考文档

应该继承哪个接口?

前 面提到,持久层接口继承 Repository 并不是唯一选择。Repository 接口是 Spring Data
的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法。与继承 Repository 等价的一种方式,就是在持久层接口上使用
@RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的:

清单 15. 两种等价的继承接口方式示例
1 public interface UserDao extends Repository<AccountInfo, Long> { …… } 
2 
3  @RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) 
4  public interface UserDao { …… }

如果持久层接口较多,且每一个接口都需要声明相似的增删改查方法,直接继承 Repository 就显得有些啰嗦,这时可以继承
CrudRepository,它会自动为域对象创建增删改查方法,供业务层直接使用。开发者只是多写了 “Crud”
四个字母,即刻便为域对象提供了开箱即用的十个增删改查方法。

但是,使用 CrudRepository
也有副作用,它可能暴露了你不希望暴露给业务层的方法。比如某些接口你只希望提供增加的操作而不希望提供删除的方法。针对这种情况,开发者只能退回到
Repository 接口,然后到 CrudRepository 中把希望保留的方法声明复制到自定义的接口中即可。

分页查询和排序是 持久层常用的功能,Spring Data 为此提供了 PagingAndSortingRepository 接口,它继承自
CrudRepository 接口,在 CrudRepository 基础上新增了两个与分页有关的方法。但是,我们很少会将自定义的持久层接口直接继承自
PagingAndSortingRepository,而是在继承 Repository 或 CrudRepository
的基础上,在自己声明的方法参数列表最后增加一个 Pageable 或 Sort 类型的参数,用于指定分页或排序信息即可,这比直接使用
PagingAndSortingRepository 提供了更大的灵活性。

JpaRepository 是继承自 PagingAndSortingRepository 的针对 JPA
技术提供的接口,它在父接口的基础上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch()
等。如果有这样的需求,则可以继承该接口。

上述四个接 口,开发者到底该如何选择?其实依据很简单,根据具体的业务需求,选择其中之一。笔者建议在通常情况下优先选择 Repository 接口。因为
Repository 接口已经能满足日常需求,其他接口能做到的在 Repository 中也能做到,彼此之间并不存在功能强弱的问题。只是
Repository 需要显示声明需要的方法,而其他则可能已经提供了相关的方法,不需要再显式声明,但如果对 Spring Data JPA
不熟悉,别人在检视代码或者接手相关代码时会有疑惑,他们不明白为什么明明在持久层接口中声明了三个方法,而在业务层使用该接口时,却发现有七八个方法可
用,从这个角度而言,应该优先考虑使用 Repository 接口。

前面提到,Spring Data JPA
在后台为持久层接口创建代理对象时,会解析方法名字,并实现相应的功能。除了通过方法名字以外,它还可以通过如下两种方式指定查询语句:

  1. Spring Data JPA 可以访问 JPA 命名查询语句。开发者只需要在定义命名查询语句时,为其指定一个符合给定格式的名字,Spring Data JPA 便会在创建代理对象时,使用该命名查询语句来实现其功能。
  2. 开发者还可以直接在声明的方法上面使用 @Query 注解,并提供一个查询语句作为参数,Spring Data JPA 在创建代理对象时,便以提供的查询语句来实现其功能。

下面我们分别讲述三种创建查询的方式。

通过解析方法名创建查询

通 过前面的例子,读者基本上对解析方法名创建查询的方式有了一个大致的了解,这也是 Spring Data JPA
吸引开发者的一个很重要的因素。该功能其实并非 Spring Data JPA 首创,而是源自一个开源的 JPA 框架 Hades,该框架的作者 Oliver
Gierke 本身又是 Spring Data JPA 项目的 Leader,所以把 Hades 的优势引入到 Spring Data JPA
也就是顺理成章的了。

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如
find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是 Sort 或者 Pageable
类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除
findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):

  • 先判断 userAddressZip (根据 POJO 规范,首字母变为小写,下同)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
  • 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
  • 接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 “AccountInfo.user.addressZip” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “AccountInfo.user.address.zip” 的值进行查询。

可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress
属性,此时会存在混淆。读者可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_AddressZip()” 或者
“findByUserAddress_Zip()”。

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA
为此提供了一些表达条件查询的关键字,大致如下:

  • And — 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
  • Or — 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
  • Between — 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
  • LessThan — 等价于 SQL 中的 “<”,比如 findBySalaryLessThan(int max);
  • GreaterThan — 等价于 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);
  • IsNull — 等价于 SQL 中的 “is null”,比如 findByUsernameIsNull();
  • IsNotNull — 等价于 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();
  • NotNull — 与 IsNotNull 等价;
  • Like — 等价于 SQL 中的 “like”,比如 findByUsernameLike(String user);
  • NotLike — 等价于 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);
  • OrderBy — 等价于 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);
  • Not — 等价于 SQL 中的 “! =”,比如 findByUsernameNot(String user);
  • In — 等价于 SQL 中的 “in”,比如 findByUsernameIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
  • NotIn — 等价于 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

使用 @Query 创建查询

@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL 查询语句即可,如下所示:

清单 16. 使用 @Query 提供自定义查询语句示例
1 public interface UserDao extends Repository<AccountInfo, Long> { 
2 
3  @Query("select a from AccountInfo a where a.accountId = ?1") 
4  public AccountInfo findByAccountId(Long accountId); 
5 
6     @Query("select a from AccountInfo a where a.balance > ?1") 
7  public Page<AccountInfo> findByBalanceGreaterThan( 
8  Integer balance,Pageable pageable); 
9  }

很多开发者在创建 JP QL 时喜欢使用命名参数来代替位置编号,@Query 也对此提供了支持。JP QL 语句中通过”:
变量”的格式来指定参数,同时在方法的参数前面使用 @Param 将方法参数与 JP QL 中的命名参数对应,示例如下:

清单 17. @Query 支持命名参数示例
 1 public interface UserDao extends Repository<AccountInfo, Long> { 
 2 
 3  public AccountInfo save(AccountInfo accountInfo); 
 4 
 5  @Query("from AccountInfo a where a.accountId = :id") 
 6  public AccountInfo findByAccountId(@Param("id")Long accountId); 
 7 
 8    @Query("from AccountInfo a where a.balance > :balance") 
 9    public Page<AccountInfo> findByBalanceGreaterThan( 
10  @Param("balance")Integer balance,Pageable pageable); 
11  }

此外,开发者也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying
来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。如下所示:

清单 18. 使用 @Modifying 将查询标识为修改查询
1 @Modifying 
2  @Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2") 
3  public int increaseSalary(int after, int before);

通过调用 JPA 命名查询语句创建查询

命名查询是 JPA 提供的一种将查询语句从方法体中独立出来,以供多个方法共用的功能。Spring Data JPA
对命名查询也提供了很好的支持。用户只需要按照 JPA 规范在 orm.xml 文件或者在代码中使用 @NamedQuery(或
@NamedNativeQuery)定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”的
命名规则。假设定义了如下接口:

清单 19. 使用 JPA 命名查询时,声明接口及方法时不需要什么特殊处理
1 public interface UserDao extends Repository<AccountInfo, Long> { 
2 
3  ...... 
4    
5  public List<AccountInfo> findTop5(); 
6  }

如果希望为 findTop5() 创建命名查询,并与之关联,我们只需要在适当的位置定义命名查询语句,并将其命名为
“AccountInfo.findTop5”,框架在创建代理类的过程中,解析到该方法时,优先查找名为 “AccountInfo.findTop5”
的命名查询定义,如果没有找到,则尝试解析方法名,根据方法名字创建查询。

创建查询的顺序

Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此,jpa:repositories
提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:

  • create — 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query 指定的查询语句,都将会被忽略。
  • create-if-not-found — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方 法名字来创建查询。这是 query-lookup-strategy 属性的默认值。
  • use-declared-query — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。

Spring Data JPA 对事务的支持

默 认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于
@Transactional(readOnly=true);增删改类型的方法,等价于
@Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。

如果用户觉得有必要,可以在接口方 法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA
提供的默认值。同时,开发者也可以在业务层方法上使用 @Transactional
指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。 具体
@Transactional 的使用,请参考 Spring 的参考文档

为接口中的部分方法提供自定义实现

有些时候,开发者可能需要在某些方法中做一些特殊的处理,此时自动生成的代理对象不能完全满足要求。为了享受 Spring Data JPA
带给我们的便利,同时又能够为部分方法提供自定义实现,我们可以采用如下的方法:

  • 将需要开发者手动实现的方法从持久层接口(假设为 AccountDao )中抽取出来,独立成一个新的接口(假设为 AccountDaoPlus ),并让 AccountDao 继承 AccountDaoPlus;
  • 为 AccountDaoPlus 提供自定义实现(假设为 AccountDaoPlusImpl );
  • 将 AccountDaoPlusImpl 配置为 Spring Bean;
  • jpa:repositories 中按清单 19 的方式进行配置。
清单 20. 指定自定义实现类
<jpa:repositories base-package="footmark.springdata.jpa.dao"> 
 <jpa:repository id="accountDao" repository-impl-ref=" accountDaoPlus " /> 
 </jpa:repositories> 

 <bean id="accountDaoPlus" class="......."/>

此外,<jpa:repositories > 提供了一个 repository-impl-postfix 属性,用以指定实现类的后缀。假设做了如下配置:

清单 21. 设置自动查找时默认的自定义实现类命名规则
1 <jpa:repositories base-package="footmark.springdata.jpa.dao"
2  repository-impl-postfix="Impl"/>

则在框架扫描到 AccountDao 接口时,它将尝试在相同的包目录下查找
AccountDaoImpl.java,如果找到,便将其中的实现方法作为最终生成的代理类中相应方法的实现。

评论

通过Ajax进行POST提交JSON类型的数据到SpringMVC Controller的方法

现在在做的项目用到了SpringMVC框架,需要从前端angular接收请求的JSON数据,为了测试方便,所以直接先用AJAX进行测试,不过刚开始用平时用的
ajax方法,提交请求会出现415或者400错误,经过研究,终于可以了,现在做个总结。

js代码:

function postSimpleData() {
        $.ajax({
            type: "POST",
            url: "Service/SimpleData",
            contentType: "application/json", //必须有
            dataType: "json", //表示返回值类型,不必须
            data: JSON.stringify({ 'foo': 'foovalue', 'bar': 'barvalue' }),  //相当于 //data: "{'str1':'foovalue', 'str2':'barvalue'}",
            success: function (jsonResult) {
                alert(jsonResult);
            }
        });
    }
    function login(){
    $.ajax({
        url: "Service/login",
        type: "POST",
        contentType: "application/json",
        dataType: "json",
        data: JSON.stringify({
            MachineIP:"127.0.0.1",
            AppTag:"4",
            RequestInfo:{
                StaffCode:"",
                Password:"",
                StaffCard:"01411"
            },
        }),
        async: true,
        success: function(data) {
            var ss = JSON.stringify(data);
            $("#result").val(ss);
            console.log(ss);
        }
    });
    }
    function postEmployees() {
        $.ajax({
            type: "POST",
            url: "Service/Employees",
            contentType: "application/json",
            dataType: "json",
            data: JSON.stringify({                "Employees": [
                                    { "firstName": "Bill", "lastName": "Gates" },
                                    { "firstName": "George", "lastName": "Bush" },
                                    { "firstName": "Thomas", "lastName": "Carter" }
                                 ]

            }),
            success: function (jsonResult) {
                alert(jsonResult);
            }
        });
    }

JAVA Controller代码:

@RequestMapping(value = "/SimpleData", method = RequestMethod.POST)
    @ResponseBody
    public ActionResult SimpleData(string foo, string bar) {
        return Json("SimpleData", JsonRequestBehavior.AllowGet);
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public ResponseProtocolMap login(@RequestBody JSONObject requestJson, HttpServletRequest request) {
        ResponseProtocolMap responseProtocolMap = null;
        String machineIP = RequestJsonUtils.getMachineIP(requestJson);
        String appTag = RequestJsonUtils.getAppTag(requestJson);
        JSONObject requestInfo = RequestJsonUtils.getRequestInfo(requestJson);
        if (requestInfo == null) {
            responseProtocolMap = new ResponseProtocolMap("-1", "参数错误");
        } else {
            String staffCode = RequestJsonUtils.getValueByKey(requestInfo, "StaffCode");
            String password = RequestJsonUtils.getValueByKey(requestInfo, "Password");
            String staffCard = RequestJsonUtils.getValueByKey(requestInfo, "StaffCard");
            responseProtocolMap = sysLoginService.login(staffCode, password, staffCard, appTag, request);
        }
        return responseProtocolMap;
    }

    @RequestMapping(value = "/Employees", method = RequestMethod.POST)
    @ResponseBody
    public ActionResult Employees(List<Employee> Employees) {
        return Json("Employees", JsonRequestBehavior.AllowGet);
    }





public class Employee{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

值得注意的有2点:

1)Ajax 选项中

contentType: "application/json"

这一条必须写,表明request的数据类型是json。

dataType: "json"

这一条表示返回值的类型,不是必须的,且依据返回值类型而定。

2)选项中

data: JSON.stringify({ 'foo': 'foovalue', 'bar': 'barvalue' })

很多时候我们将数据写作:

{ 'foo': 'foovalue', 'bar': 'barvalue' }

这样会导致错误,因为js会默认将这个json对象放到表单数据中,故而导致controller接收不到。

有两种办法处理:第一种方式是用JSON.stringify()函数,其中JSON被Ecmascript5定义为全局对象。

第二种方式是直接用双引号包裹起来,比如data: “{‘str1’:’foovalue’, ‘str2’:’barvalue’}”。

评论

面试感悟----一名3年工作经验的程序员应该具备的技能

由于lz最近也准备换一个工作了,所以在网上看到了这一片文章,觉得挺好就转出来分享给大家。

原文地址http://www.cnblogs.com/xrq730/p/5260294.html,转载请注明出处,谢谢!

前言

因为和同事有约定再加上LZ自己也喜欢做完一件事之后进行总结,因此有了这篇文章。这篇文章大部分内容都是面向整个程序员群体的,当然因为LZ本身是做Java开发的
,因此有一部分内容也是专门面向咱们Java程序员的。

简单先说一下,LZ坐标杭州,13届本科毕业,算上年前在阿里巴巴B2B事业部的面试,一共有面试了有6家公司(因为LZ不想请假,因此只是每个晚上去其他公司面试,
所以面试的公司比较少),其中成功的有4家,另外两家失败的原因在于:

1、阿里巴巴B2B事业部的面试,两轮技术面试都过了,最后一轮面试是对方的主管,由于听说技术面试过了基本上90%都面试成功了,所以LZ在和主管的交谈中也是毫无
顾忌,说得天花乱坠,很多自己介于知道和不知道的东西都直接脱口而出了,结果多次被对方一反问就问得哑口无言。事后想来,模棱两可的答案是面试中最忌讳的,这次的失败
也让LZ认真地对待后面的每一次面试

2、另外一家失败的是一家小公司,也就20来个人吧,整个团队是支付宝出来创业的,非常厉害。面试完LZ多方了解了一下,对方认为我基本功什么的都不错,但是实际项目
经验还是欠缺一些,因为对方是创业型公司,需要人上手就能干活,因此我在这个时候还不是特别适合他们团队

至于其他成功的四家公司,给LZ的面试评价都挺高的貌似,但LZ也不想记流水账,因此就不一一列举每家公司的面试过程了,下面LZ主要谈谈作为一名工作三年左右的Ja
va程序员应该具备的一些技能以及个人的一些其他感悟。

关于程序员的几个阶段

每个程序员、或者说每个工作者都应该有自己的职业规划,如果看到这里的朋友没有自己的职业规划,希望你可以思考一下自己的将来。

LZ常常思考自己的未来,也从自己的思考中总结出了一些东西,作为第一部分来谈谈。LZ认为一名程序员应该有几个阶段(以下时间都算上实习期):

  • 第一阶段—-三年

    • 我认为三年对于程序员来说是第一个门槛,这个阶段将会淘汰掉一批不适合写代码的人。这一阶段,我们走出校园,迈入社会,成为一名程序员,正式从书本上的内容迈向真正的企业级开发。我们知道如何团队协作、如何使用项目管理工具、项目版本如何控制、我们写的代码如何测试如何在线上运行等等,积累了一定的开发经验,也对代码有了一定深入的认识,是一个比较纯粹的Coder的阶段
  • 第二阶段—-五年

    • 五年又是区分程序员的第二个门槛。有些人在三年里,除了完成工作,在空余时间基本不会研究别的东西,这些人永远就是个Coder,年纪大一些势必被更年轻的人给顶替;有些人在三年里,除了写代码之外,还热衷于研究各种技术实现细节、看了N多好书、写一些博客、在Github上分享技术,这些人在五年后必然具备在技术上独当一面的能力并且清楚自己未来的发展方向,从一个Coder逐步走向系统分析师或是架构师,成为项目组中不可或缺的人物
  • 第三阶段—-十年

    • 十年又是另一个门槛了,转行或是继续做一名程序员就在这个节点上。如果在前几年就抱定不转行的思路并且为之努力的话,那么在十年的这个节点上,有些人必然成长为一名对行业有着深入认识、对技术有着深入认识、能从零开始对一个产品进行分析的程序员,这样的人在公司基本担任的都是CTO、技术专家、首席架构师等最关键的职位,这对于自己绝对是一件荣耀的事,当然老板在经济上也绝不会亏待你

第一部分总结一下,我认为,随着你工作年限的增长、对生活对生命认识的深入,应当不断思考三个问题:

  1. 我到底适不适合当一名程序员?
  2. 我到底应不应该一辈子以程序员为职业?
  3. 我对编程到底持有的是一种什么样的态度,是够用就好呢还是不断研究?

最终,明确自己的职业规划,对自己的规划负责并为之努力。

关于项目经验

LZ在网上经常看到一些别的朋友有提出项目经验的问题,依照LZ面试的感觉来说,面试主要看几点: 项目经验+基本技术+个人潜力
(也就是值不值得培养)。

关于项目经验,我认为并发编程网的创始人方腾飞老师讲的一段话非常好:

介绍产品时面试官会考察应聘者的沟通能力和思考能力,我们大部分情况都是做产品的一个功能或一个模块,但是即使是这样,自己有没有把整个系统架构或产品搞清楚,并能介绍清楚,为什么做这个系统?这个系统的价值是什么?这个系统有哪些功能?优缺点有哪些?如果让你重新设计这个系统你会如何设计?  

我觉得这就已经足以概括了。也许你仅仅工作一年,也许你做的是项目中微不足道的模块,当然这些一定是你的劣势且无法改变,但是如何弥补这个劣势,从方老师的话中我总结
几点:

  1. 明确你的项目到底是做什么的,有哪些功能
  2. 明确你的项目的整体架构,在面试的时候能够清楚地画给面试官看并且清楚地指出从哪里调用到哪里、使用什么方式调用
  3. 明确你的模块在整个项目中所处的位置及作用
  4. 明确你的模块用到了哪些技术,更好一些的可以再了解一下整个项目用到了哪些技术

在你无法改变自己的工作年限、自己的不那么有说服力的项目经验的情况下(这一定是扣分项),可以通过这种方式来一定程度上地弥补并且增进面试官对你的好感度。

补充一点,在面试中聊你的项目的时候,有一个问题90%是绕不过的: 谈一下你在项目中解决过的比较复杂的问题 。这需要在工作中不断去发现和探索,不
需要多,在你自己目前的项目中只要你找到一两个能说的问题就行。一个小技巧是,即使问题不是你解决的而是别人解决的,但是你把这个问题弄懂、搞透了,在面试的时候你一
样可以把这个问题当作是你自己解决的来说—-毕竟,谁来管这个问题当时到底是不是你解决的呢?

关于专业技能

写完项目接着写写一名3年工作经验的Java程序员应该具备的技能,这可能是Java程序员们比较关心的内容。我这里要说明一下,以下列举的内容不是都要会的东西—-
但是如果你掌握得越多,最终能得到的评价、拿到的薪水势必也越高。

1、基本语法

这包括static、final、transient等关键字的作用,foreach循环的原理等等。今天面试我问你static关键字有哪些作用,如果你答出sta
tic修饰变量、修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满意,因为能看出你非常热衷研究技术。

最深入的一次,LZ记得面试官直接问到了我volatile关键字的底层实现原理(顺便插一句,面试和被面试本身就是相对的,面试官能问这个问题同时也让面试者感觉到
面试官也是一个喜爱研究技术的人,增加了面试者对公司的好感,LZ最终选择的就是问了这个问题的公司),不要觉得这太吹毛求疵了—-越简单的问题越能看出一个人的水平
,别人对你技术的考量绝大多数都是以 深度 优先、广度次之 为标准的,切记。

2、集合

非常重要,也是必问的内容。基本上就是List、Map、Set,问的是各种实现类的底层实现原理,实现类的优缺点。

集合要掌握的是ArrayList、LinkedList、Hashtable、HashMap、ConcurrentHashMap、HashSet的实现原理,能
流利作答,当然能掌握CopyOnWrite容器和Queue是再好不过的了。另外多说一句,ConcurrentHashMap的问题在面试中问得特别多,大概是因
为这个类可以衍生出非常多的问题,关于ConcurrentHashMap,我给网友朋友们提供三点回答或者是研究方向:

(1)ConcurrentHashMap的锁分段技术

(2)ConcurrentHashMap的读是否要加锁,为什么

(3)ConcurrentHashMap的迭代器是强一致性的迭代器还是弱一致性的迭代器

3、设计模式

本来以为蛮重要的一块内容,结果只在阿里巴巴B2B事业部面试的时候被问了一次,当时问的是装饰器模式。

当然咱们不能这么功利,为了面试而学习,设计模式在工作中还是非常重要、非常有用的,23种设计模式中重点研究常用的十来种就可以了,面试中关于设计模式的问答主要是
三个方向:

(1)你的项目中用到了哪些设计模式,如何使用

(2)知道常用设计模式的优缺点

(3)能画出常用设计模式的UML图

4、多线程

这也是必问的一块了。因为三年工作经验,所以基本上不会再问你怎么实现多线程了,会问得深入一些比如说Thread和Runnable的区别和联系、多次start一
个线程会怎么样、线程有哪些状态。当然这只是最基本的,出乎意料地,几次面试几乎都被同时问到了一个问题,问法不尽相同,总结起来是这么一个意思:

假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?

聪明的网友们对这个问题是否有答案呢?不难,java.util.concurrent下就有现成的类可以使用。

另外,线程池也是比较常问的一块,常用的线程池有几种?这几种线程池之间有什么区别和联系?线程池的实现原理是怎么样的?实际一些的,会给你一些具体的场景,让你回答
这种场景该使用什么样的线程池比较合适。

最后,虽然这次面试问得不多,但是多线程同步、锁这块也是重点。synchronized和ReentrantLock的区别、synchronized锁普通方法和
锁静态方法、死锁的原理及排查方法等等,关于多线程,我在之前有些过文章总结过多线程的40个问题,可以参看 40个Java多线程问题总结

5、IO

再次补充IO的内容,之前忘了写了。

IO分为File IO和Socket IO,File IO基本上是不会问的,问也问不出什么来,平时会用就好了,另外记得File IO都是阻塞IO。

Socket
IO是比较重要的一块,要搞懂的是阻塞/非阻塞的区别、同步/异步的区别,借此理解阻塞IO、非阻塞IO、多路复用IO、异步IO这四种IO模型,Socket IO
如何和这四种模型相关联。这是基本一些的,深入一些的话,就会问NIO的原理、NIO属于哪种IO模型、NIO的三大组成等等,这有些难,当时我也是研究了很久才搞懂
NIO。提一句, NIO并不是严格意义上的非阻塞IO而应该属于多路复用IO
,面试回答的时候要注意这个细节,讲到NIO会阻塞在Selector的select方法上会增加面试官对你的好感。

如果用过Netty,可能会问一些Netty的东西,毕竟这个框架基本属于当前最好的NIO框架了(Mina其实也不错,不过总体来说还是比不上Netty的),大多
数互联网公司也都在用Netty。

6、JDK源码

要想拿高工资,JDK源码不可不读。上面的内容可能还和具体场景联系起来,JDK源码就是实打实地看你平时是不是爱钻研了。LZ面试过程中被问了不少JDK源码的问题
,其中最刁钻的一个问了LZ,String的hashCode()方法是怎么实现的,幸好LZ平时String源代码看得多,答了个大概。JDK源码其实没什么好总结
的,纯粹看个人,总结一下比较重要的源码:

(1)List、Map、Set实现类的源代码

(2)ReentrantLock、AQS的源代码

(3)AtomicInteger的实现原理,主要能说清楚CAS机制并且AtomicInteger是如何利用CAS机制实现的

(4)线程池的实现原理

(5)Object类中的方法以及每个方法的作用

这些其实要求蛮高的,LZ去年一整年基本把JDK中重要类的源代码研究了个遍,真的花费时间、花费精力,当然回头看,是值得的—-不仅仅是为了应付面试。

7、框架

老生常谈,面试必问的东西。一般来说会问你一下你们项目中使用的框架,然后给你一些场景问你用框架怎么做,比如我想要在Spring初始化bean的时候做一些事情该
怎么做、想要在bean销毁的时候做一些事情该怎么做、MyBatis中$和#的区别等等,这些都比较实际了,平时积累得好、有多学习框架的使用细节自然都不成问题。

如果上面你的问题答得好,面试官往往会深入地问一些框架的实现原理。问得最多的就是Spring AOP的实现原理,当然这个很简单啦,两句话就搞定的的事儿,即使你
不会准备一下就好了。LZ遇到的最变态的是让LZ画一下Spring的Bean工厂实现的UML图,当然面对这样一个有深度的问题,LZ是绝对答不出来的/(ㄒoㄒ)
/~~

8、数据库

数据库十有八九也都会问到。一些基本的像union和union all的区别、left join、几种索引及其区别就不谈了,比较重要的就是数据库性能的优化,如
果对于数据库的性能优化一窍不通,那么有时间,还是建议你在面试前花一两天专门把SQL基础和SQL优化的内容准备一下。

不过数据库倒是不用担心,一家公司往往有很多部门,如果你对数据库不熟悉而基本技术又非常好,九成都是会要你的,估计会先把你放到对数据库使用不是要求非常高的部门锻
炼一下。

9、数据结构和算法分析

数据结构和算法分析,对于一名程序员来说,会比不会好而且在工作中绝对能派上用场。数组、链表是基础,栈和队列深入一些但也不难,树挺重要的,比较重要的树AVL树、
红黑树,可以不了解它们的具体实现,但是要知道什么是二叉查找树、什么是平衡树,AVL树和红黑树的区别。记得某次面试,某个面试官和我聊到了数据库的索引,他问我:

你知道索引使用的是哪种数据结构实现吗?

LZ答到用的Hash表吧,答错。他又问,你知道为什么要使用树吗?LZ答到因为Hash表可能会出现比较多的冲突,在千万甚至是上亿级别的数据面前,会大大增加查找
的时间复杂度。而树比较稳定,基本保证最多二三十次就能找到想要的数据,对方说不完全对,最后我们还是交流了一下这个问题,我也明白了为什么要使用树,这里不说,网友
朋友们觉得索引为什么要使用树来实现呢?

至于算法分析,不会、不想研究就算了,记得某次面试对方问我,Collections.sort方法使用的是哪种排序方法,额,吐血三升。当然为了显示LZ的博学,对
算法分析也有一定的研究(⊙﹏⊙)b,LZ还是硬着头皮说了一句可能是冒泡排序吧。当然答案肯定不是,有兴趣的网友朋友们可以去看一下Collections.sor
t方法的源代码,用的是一种叫做TimSort的排序法,也就是增强型的归并排序法。

10、Java虚拟机

出乎LZ的意料,Java虚拟机应该是很重要的一块内容,结果在这几家公司中被问到的概率几乎为0。要知道,LZ去年可是花了大量的时间去研究Java虚拟机的,光周
志明老师的《 深入理解Java虚拟机:JVM高级特性与最佳实践 》,LZ就读了不下五遍。

言归正传,虽然Java虚拟机没问到,但我觉得还是有必要研究的,LZ就简单地列一个提纲吧,谈谈Java虚拟机中比较重要的内容:

  1. Java虚拟机的内存布局
  2. GC算法及几种垃圾收集器
  3. 类加载机制,也就是双亲委派模型
  4. Java内存模型
  5. happens-before规则
  6. volatile关键字使用规则

也许面试无用,但在走向大牛的路上,不可不会。

11、Web方面的一些问题

Java主要面向Web端,因此Web的一些问题也是必问的。LZ碰到过问得最多的两个问题是:

谈谈分布式Session的几种实现方式

常用的四种能答出来自然是让面试官非常满意的,另外一个常问的问题是:

讲一下Session和Cookie的区别和联系以及Session的实现原理  

这两个问题之外,web.xml里面的内容是重点,Filter、Servlet、Listener,不说对它们的实现原理一清二楚吧,至少能对它们的使用知根知底。
另外,一些细节的方面比如get/post的区别、forward/重定向的区别、HTTPS的实现原理也都可能会被考察到。

噢,想起来了,一致性Hash算法貌似也被问到了几次,这个LZ以前专门深入研究过并且写了两篇博文,因此问到这个问题LZ自然是答得毫不费力。文章是
MemCache详细解读
和对
一致性Hash算法,Java代码实现的深入研究

,特别说明,LZ真的不是在为自已以前写的文章打广告啊啊啊啊啊啊。

最后,如果有兴趣有时间,建议学习、研究一下SOA和RPC,面向服务体系,大型分布式架构必备,救命良方、包治百病、屡试不爽。

关于HR面试

如果你过五关斩六将,成功地通过了所有的技术面,那么恭喜你,你离升职加薪、出任CEO、迎娶白富美、走向人生巅峰又进了一步。但是还没有到谈薪资待遇的时候,最后还
有一个考验:HR面试。基本所有的大公司都有这一轮的面试,不要小看HR面试,很多公司的HR对于面试者都有一票否决权的—-即使前面的面试对你的评价再高。

所以,这轮的面试也必须重视起来,HR面试主要问的是几点:

  1. 简历中写的过去工作经历的离职原因
  2. 当前公司薪资待遇
  3. 期望能到怎样的一家公司
  4. 个人未来的发展方向

我专门提一下第2点。可能有人比较排斥也不想说这个,我个人倒是持开放状态,问了就说了,当然一些的夸大还是必要的,当前公司薪资待遇多报个一千块钱完全没问题(毕竟
是一家互联网公司总多多少少有些补贴啊什么的嘛)。因为这和你在新公司能拿到的薪水关系不大,新公司能拿到的薪水的决定因素是整个公司的薪资情况以及根据你的面试情况
在公司的定位,都是有固定的薪资范围的。HR问这个主要也就是心里有个数并且看你是否诚信—-有些公司入职时会要求你提供最近一家单位的银行流水号。

HR面试就说到这里了,总结起来其实就是四个字: 滴水不漏
。整个面试过程态度积极向上,不要有任何悲观消极的态度(尤其在谈到以前公司情况的时候,即使有再多的不满),就不会有问题。

关于面试心态

这个嘛,LZ其实在公司也面试过几个人,一半以上的面试者回答问题的时候都属于那种双腿发抖、声音颤抖的类型。在LZ看来这大可不必并且这还是扣分项,回答问题的时候
最最基本的两个要求:

  1. 不紧不慢,平心静气
  2. 条理清晰

表达能力绝对是面试的时候重要的考察项目。咱们做的是程序员这一行,讲究的是团队协作,不是写作、画画,一支笔、一个人就行了,一个表达能力不行的程序员,要来又有什
么用呢?

除此之外,就是保持良好的心态。古语说得好,只要功夫深,铁杵磨成针,面试的成功与否,在于平时的积累,临时抱抱佛脚,看两道面试题是没有用的,只要平时足够努力,成
功是水到渠成的事情,平时不怎么研究技术的,那也就是个听天由命的事情,只要充分地展示平时自己的所学就可以了。

因此在我看来,不要把面试当作面试,当做一次技术交流,把 面试的心态从我要找到一份工作转变为我要通过面试去发现不足、提升自己
,这样就会平和多了,即使失败也不会有太多失望的感觉。

另外,如果平时自己热衷于研究技术的朋友,真的要有自信,不要觉得别人面试你别人就比你厉害。面试官未必比你优秀,他问的问题往往都是他平时研究得比较多的问题,你一
样有很多自己的研究面试官未必知道。

关于Java

网上常看到一种说法:Java比较简单。某种程度上这会打击Java程序员的信心—-原来咱们平时用的是这种小儿科的玩意儿啊,在我看来这种想法大可不必,这一部分我
来讲讲对于这个话题的看法。

这种说法有些片面,得分开两部分来看,我用四个自总结一下就是: 易学难精

1、易学部分

Java易学我认为有两部分的原因:

  • 很多培训公司包括大四的学生找工作都会学习Java,绝大多数是因为易学。Java从C/C++发展而来,感谢前人的智慧,它消除了C/C++中最复杂和让人困惑的语法、它消除了平台的差异性、它不需要用户手动释放内存空间、它避免了Java程序员和本地语言的交互,让程序员只需要专注于语法层面和应用层面
  • Java作为一门面向对象的语言,在企业级开发中体现出了它无与伦比的特性,整个开发流程比较固定化、模块化,需求分析起来也相对容易。我举个自己以前的例子吧,我在大一学习C语言的时候,用C语言写了一个图书管理系统写了2000行+的代码,大四学了C++之后,用面向对象的语言C++取代面向过程的语言C语言重新写了一个功能相似的图书管理系统,只写了1100行的样子,这就是面向对象的优势

2、难精部分

接着咱们聊聊难精的部分。

Java语言的设计者帮助Java程序员做了这么多事情,这有利也有弊。有利的部分前面已经说过了,让Java易学,不过有弊的部分同样明显。假如在应用运行过程中遇
到了语法层面和应用层面之外的错误,应当如何处理?比如线上环境出现内存溢出怎么办?GC时间过长怎么办?IO长时间没反应怎么办?方法抛出莫名其妙的异常怎么办?

凡此种种,绝不是一名只会写几个if…else…的Java程序员就可以解决的,这需要大量的经历、大量的实践、大量对Java底层实现细节的研究,而这往往是最难、
最考验Java程序员的部分,一些人根本就不想往深去研究,另外一些人研究了一点点就研究不下去了。

Java为什么难精?就是这个原因。除非你水平特别高,否则五年工作经验以下的Java程序员在简历上写”精通Java”绝对是一件非常愚蠢的事情。

结语

文章写到这里,感觉有点像鸡汤文了,那就以最后的鸡汤作为结尾吧。

在以前博客园的一篇文章中,讲到了奔三程序员的困惑,大致说的是三十岁之后程序员要转行之类的云云,LZ在博文中留下了如下的评论:

就以这段话自勉、共勉吧。越努力、越幸运, 如果你不是官二代、富二代、红二代, 那么请记住: 勤奋才是改变你命运的唯一捷径

** ==================================================================================

我不能保证写的每个地方都是对的,但是至少能保证不复制、不黏贴,保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于
技术、对于生活的态度。

我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。

** 其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。

==============================================================================
====

评论

分布式日志收集之Logstash 笔记(一)

(一)logstash是什么?

logstash是一种分布式日志收集框架,开发语言是JRuby,当然是为了与Java平台对接,不过与Ruby语法兼容良好,非常简洁强大,经常与Elastic
Search,Kibana配置,组成著名的ELK技术栈,非常适合用来做日志数据的分析。

当然它可以单独出现,作为日志收集软件,你可以收集日志到多种存储系统或临时中转系统,如MySQL,redis,kakfa,HDFS,
lucene,solr等并不一定是ElasticSearch。

官网下载地址:https://www.elastic.co/downloads/logstash
官网文档地址:https://www.elastic.co/guide/en/logstash/current/index.html

(二)logstash的的安装

logstash的目前的最新版本是2.0.0,建议安装在Linux平台,虽然它也支持Windows平台,但可能会有问题
下载:
wget https://download.elastic.co/logstash/logstash/logstash-2.0.0.tar.gz

解压:
tar -zxvf logstash-2.0.0.tar.gz

进入根目录,执行bin/logstash -h 可查看帮助文档
参数介绍如下:

使用命令模板:
/bin/logstash 命令参数 选项

选项:
-f , 指定加载一个后缀为.conf文件的logstash配置模块
-e , 命令行指定参数 , 通常用来调试
-w, 指定logstash的工作线程数
-l, 指定logstash的默认日志写入到一个文件中,如果不指定,默认是标准输出
--quiet 静默模式,仅仅只有error级别信息输出
--verbose info级别的log输出
--debug debug 级别的log输出.
-V, –version 查看logstash的版本
-p, –pluginpath PATH 加载自定义的logstash插件
-t, –configtest 检查logstash配置是否有效
-h, –help 打印帮助

(三)logstash的数据处理模型

(1)input =》 output
(2)input =》 filter =》 output

其中input常用的输入源有:file,syslog,redis,log4j,apache log或nginx
log,或者其他一些自定义的log格式,业务log,搜索log,订单log等等

filter常用的选项有:
grok:支持正则提取任何非结构化数据或结构化数据,其中logstash内置120多种正则,比如常见的时间,ip,用户名,等等也支持自定义正则解析
mutate:修改字段名,删除,更新等操作,转换字段类型等
drop: 删除某些时间,如debug
clone:拷贝一份事件副本,用来添加或删除字段
geoip : 通过ip获取地理位置信息,在做kibana区域统计图非常炫
ruby: 支持原生的ruby代码,操作事件,实现强大的其他功能

output常用的输出有:
elasticsearch 比较常用
file:写入文件
redis:写入队列
hdfs:写入HDFS,需插件支持
zabbix: zabbix监控
mongodb:写入mongodb库

除此之外还有个编码插件codecs也比较常用
常用来处理json数据或者多行数据源

(四)logstash一些简单例子

(1)使用命令行命令调试:

[search@h3 logstash-2.0.0]$ bin/logstash -e "input{stdin{}} output{stdout{}}"  
Default settings used: Filter workers: 1  
Logstash startup completed  
hello  
2015-11-04T15:16:02.747Z h3 hello  
test  
2015-11-04T15:16:08.108Z h3 test

(2)命令行参数仅适合简单的配置,如果配置比较多,我们一般会写入一个以.conf结尾的配置文件里,然后使用
-f命令加载,将(1)中的配置,写入hello.conf

然后使用bin/logstash -f hello.conf 执行加载,即可达到同样效果

(3)常用的数据模块

<pre name="code" class="java">input{     
.....     
}     
filter{     
......     
}     
output{     
.......     
}    

(4)监听文件,支持通配符,写入文件

input{     

file => ["/var/log/file","/var/solr/log/*"]     

}     

output{     

file => "/sys/collect/log"    


}

(5)logstash插件支持数据类型

数组: path=> ["a","b"]     
布尔:ssl_enable => true    
字节:     
my_bytes =>"1113"#1113 bytes      
my_bytes =>"10MiB"#10485760 bytes     
my_bytes =>"100kib"#102400 bytes     
my_bytes =>"180 mb"#180000000 bytes     
编码:     
codec => "json"    
哈希表:     
match => {     

"k1" => "v1"    
"k2" => "v2"    
"k3" => "v3"    

}     
数值:     
port=> 33    
密码:     
pwd=> "password"    
路径:     
path=> "/tmp/logstash"    
字符串:     
name => "hello wordld"    
注释:     
input{     
# 号开头,与shell脚本注释一样     
}
评论

从MVC到前后端分离(REST-个人也认为是目前比较流行和比较好的方式)

摘要: MVC模式早在上个世纪70年代就诞生了,直到今天它依然存在,可见生命力相当之强。MVC模式最早用于Smalltalk语言中,最后在其它许多开发语言中都得到了很好的应用,例如,Java中的Struts、Spring MVC等框架。

1. 理解MVC

MVC是一种经典的设计模式,全名为Model-View-Controller,即模型-视图-控制器。

其中,模型是用于封装数据的载体,例如,在 Java
中一般通过一个简单的POJO(Plain Ordinary Java Object)来表示,其本质是一个普通的Java Bean,包含一系列的成员变量及其g
etter/setter方法。对于视图而言,它更加偏重于展现,也就是说,视图决定了界面到底长什么样子,在Java中可通过JSP来充当视图,或者通过纯HTML
的方式进行展现,而后者才是目前的主流。模型和视图需要通过控制器来进行粘合,例如,用户发送一个HTTP请求,此时该请求首先会进入控制器,然后控制器去获取数据并
将其封装为模型,最后将模型传递到视图中进行展现。

综上所述,MVC的交互过程如图1所示。

2. MVC模式的优点与不足

MVC模式早在上个世纪70年代就诞生了,直到今天它依然存在,可见生命力相当之强。MVC模式最早用于Smalltalk语言中,最后在其它许多开发语言中都得到了
很好的应用,例如,Java中的Struts、Spring
MVC等框架。正是因为这些MVC框架的出现,才让MVC模式真正落地,让开发更加高效,让代码耦合度尽量减小,让应用程序各部分的职责更加清晰。

既然MVC模式这么好,难道它就没有不足的地方吗?我认为MVC至少有以下三点不足:

  1. 每次请求必须经过“控制器->模型->视图”这个流程,用户才能看到最终的展现的界面,这个过程似乎有些复杂。
  2. 实际上视图是依赖于模型的,换句话说,如果没有模型,视图也无法呈现出最终的效果。
  3. 渲染视图的过程是在服务端来完成的,最终呈现给浏览器的是带有模型的视图页面,性能无法得到很好的优化。

为了使数据展现过程更加直接,并且提供更好的用户体验,我们有必要对MVC模式进行改进。不妨这样来尝试,首先从浏览器发送AJAX请求,然后服务端接受该请求并返回
JSON数据返回给浏览器,最后在浏览器中进行界面渲染。

改进后的MVC模式如图2所示。

也就是说,我们输入的是AJAX请求,输出的是JSON数据,市面上有这样的技术来实现这个功能吗?答案是REST。

REST全称是Representational State Transfer(表述性状态转移),它是Roy
Fielding博士在2000年写的一篇关于软件 架构
风格的论文,此文一出,威震四方!国内外许多知名互联网公司纷纷开始采用这种轻量级的Web服务,大家习惯将其称为RESTful Web
Services,或简称REST服务。]

如果将浏览器这一端视为前端,而服务器那一端视为后端的话,可以将以上改进后的MVC模式简化为以下前后端分离模式,如图3所示。

可见,有了REST服务,前端关注界面展现,后端关注业务逻辑,分工明确,职责清晰。那么,如何使用REST服务将应用程序进行前后端分离呢?我们接下来继续探讨,首
先我们需要认识REST。

3. 认识REST

REST本质上是使用URL来访问资源种方式。众所周知,URL就是我们平常使用的请求地址了,其中包括两部分:请求方式与请求路径,比较常见的请求方式是GET与P
OST,但在REST中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四种,正好与
CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELET
E(删),这正是REST与CRUD的异曲同工之妙!需要强调的是,REST是“面向资源”的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我
们经常通过领域对象来进行数据建模。

REST是一个“无状态”的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。也就是说,服务端将
内部资源发布REST服务,客户端通过URL来访问这些资源,这不就是SOA所提倡的“面向服务”的思想吗?所以,REST也被人们看做是一种“轻量级”的SOA实现
技术,因此在企业级应用与互联网应用中都得到了广泛应用。

下面我们举几个例子对REST请求进行简单描述:

可见,请求路径相同,但请求方式不同,所代表的业务操作也不同,例如,/advertiser/1这个请求,带有GET、PUT、DELETE三种不同的请求方式,对
应三种不同的业务操作。

虽然REST看起来还是很简单的,实际上我们往往需要提供一个REST框架,让其实现前后端分离架构,让开发人员将精力集中在业务上,而并非那些具体的技术细节。下面
我们将使用Java技术来实现这个REST框架,整体框架会基于Spring进行开发。

4. 实现REST框架

4.1 统一响应结构

使用REST框架实现前后端分离架构,我们需要首先确定返回的JSON响应结构是统一的,也就是说,每个REST请求将返回相同结构的JSON响应结构。不妨定义一个
相对通用的JSON响应结构,其中包含两部分:元数据与返回值,其中,元数据表示操作是否成功与返回值消息等,返回值对应服务端方法所返回的数据。该JSON响应结构
如下:

{
    "meta": {
        "success": true,
        "message": "ok"
    },
    "data": ...
}

为了在框架中映射以上JSON响应结构,我们需要编写一个Response类与其对应:

[java] view plain copy

  1. public class Response {
    1. private static final String OK = “ok” ;
  2. private static final String ERROR = “error” ;
    1. private Meta meta;
  3. private Object data;
    1. public Response success() {
  4. this .meta = new Meta( true , OK);
  5. return this ;
  6. }
    1. public Response success(Object data) {
  7. this .meta = new Meta( true , OK);
  8. this .data = data;
  9. return this ;
  10. }
    1. public Response failure() {
  11. this .meta = new Meta( false , ERROR);
  12. return this ;
  13. }
    1. public Response failure(String message) {
  14. this .meta = new Meta( false , message);
  15. return this ;
  16. }
    1. public Meta getMeta() {
  17. return meta;
  18. }
    1. public Object getData() {
  19. return data;
  20. }
    1. public class Meta {
    1. private boolean success;
  21. private String message;
    1. public Meta( boolean success) {
  22. this .success = success;
  23. }
    1. public Meta( boolean success, String message) {
  24. this .success = success;
  25. this .message = message;
  26. }
    1. public boolean isSuccess() {
  27. return success;
  28. }
    1. public String getMessage() {
  29. return message;
  30. }
  31. }
  32. }

以上Response类包括两类通用返回值消息:ok与error,还包括两个常用的操作方法:success( )与failure(
),通过一个内部类来展现元数据结构,我们在下文中多次会使用该Response类。

实现该REST框架需要考虑许多问题,首当其冲的就是对象序列化问题。

4.2 实现对象序列化

想要解释什么是对象序列化?不妨通过一些例子进行说明。比如,通过浏览器发送了一个普通的HTTP请求,该请求携带了一个JSON格式的参数,在服务端需要将该JSO
N参数转换为普通的Java对象,这个转换过程称为序列化。再比如,在服务端获取了数据,此时该数据是一个普通的Java对象,然后需要将这个Java对象转换为JS
ON字符串,并将其返回到浏览器中进行渲染,这个转换过程称为反序列化。不管是序列化还是反序列化,我们一般都称为序列化。

实际上,Spring
MVC已经为我们提供了这类序列化特性,只需在Controller的方法参数中使用@RequestBody注解定义需要反序列化的参数即可,如以下代码片段:

[java] view plain copy

  1. @Controller
  2. public class AdvertiserController {
    1. @RequestMapping (value = “/advertiser” , method = RequestMethod.POST)
  3. public Response createAdvertiser( @RequestBody AdvertiserParam advertiserParam) {
  4. }
  5. }

若需要对Controller的方法返回值进行序列化,则需要在该返回值上使用@ResponseBody注解来定义,如以下代码片段:

[java] view plain copy

  1. @Controller
  2. public class AdvertiserController {
    1. @RequestMapping (value = “/advertiser/{id}” , method = RequestMethod.GET)
  3. public @ResponseBody Response getAdvertiser( @PathVariable ( “id” ) String advertiserId) {
  4. }
  5. }

当然,@ResponseBody注解也可以定义在类上,这样所有的方法都继承了该特性。由于经常会使用到@ResponseBody注解,所以Spring提供了一
个名为@RestController的注解来取代以上的@Controller注解,这样我们就可以省略返回值前面的@ResponseBody注解了,但参数前面
的@RequestBody注解是无法省略的。实际上,看看Spring中对应@RestController注解的源码便可知晓:

[java] view plain copy

  1. @Target ({ElementType.TYPE})
  2. @Retention (RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Controller
  5. @ResponseBody
  6. public @interface RestController {
    1. String value() default “” ;
  7. }

可见,@RestController注解已经被@Controller与@ResponseBody注解定义过了,Spring框架会识别这类注解。需要注意的是,
该特性在Spring 4.0中才引入。

因此,我们可将以上代码进行如下改写:

[java] view plain copy

  1. @RestController
  2. public class AdvertiserController {
    1. @RequestMapping (value = “/advertiser” , method = RequestMethod.POST)
  3. public Response createAdvertiser( @RequestBody AdvertiserParam advertiserParam) {
  4. }
    1. @RequestMapping (value = “/advertiser/{id}” , method = RequestMethod.GET)
  5. public Response getAdvertiser( @PathVariable ( “id” ) String advertiserId) {
  6. }
  7. }

除了使用注解来定义序列化行为以外,我们还需要使用Jackson来提供JSON的序列化操作,在Spring配置文件中只需添加以下配置即可:

[xml] view plain copy

  1. < mvc:annotation-driven >
  2. < mvc:message-converters >
  3. < bean class = “org.springframework.http.converter.json.MappingJackson2HttpMessageConverter” />
  4. </ mvc:message-converters >
  5. </ mvc:annotation-driven >

若需要对Jackson的序列化行为进行定制,比如,排除值为空属性、进行缩进输出、将驼峰转为下划线、进行日期格式化等,这又如何实现呢?

首先,我们需要扩展Jackson提供的ObjectMapper类,代码如下:

[java] view plain copy

  1. public class CustomObjectMapper extends ObjectMapper {
    1. private boolean camelCaseToLowerCaseWithUnderscores = false ;
  2. private String dateFormatPattern;
    1. public void setCamelCaseToLowerCaseWithUnderscores( boolean camelCaseToLowerCaseWithUnderscores) {
  3. this .camelCaseToLowerCaseWithUnderscores = camelCaseToLowerCaseWithUnderscores;
  4. }
    1. public void setDateFormatPattern(String dateFormatPattern) {
  5. this .dateFormatPattern = dateFormatPattern;
  6. }
    1. public void init() {
  7. // 排除值为空属性
  8. setSerializationInclusion(JsonInclude.Include.NON_NULL);
  9. // 进行缩进输出
  10. configure(SerializationFeature.INDENT_OUTPUT, true );
  11. // 将驼峰转为下划线
  12. if (camelCaseToLowerCaseWithUnderscores) {
  13. setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
  14. }
  15. // 进行日期格式化
  16. if (StringUtil.isNotEmpty(dateFormatPattern)) {
  17. DateFormat dateFormat = new SimpleDateFormat(dateFormatPattern);
  18. setDateFormat(dateFormat);
  19. }
  20. }
  21. }

然后,将CustomObjectMapper注入到MappingJackson2HttpMessageConverter中,Spring配置如下:

[xml] view plain copy

  1. < bean id = “objectMapper” class = “com.xxx.api.json.CustomObjectMapper” init-method = “init” >
  2. < property name = “camelCaseToLowerCaseWithUnderscores” value = “true” />
  3. < property name = “dateFormatPattern” value = “yyyy-MM-dd HH:mm:ss” />
  4. </ bean >
    1. < mvc:annotation-driven >
  5. < mvc:message-converters >
  6. < bean class = “org.springframework.http.converter.json.MappingJackson2HttpMessageConverter” >
  7. < property name = “objectMapper” ref = “objectMapper” />
  8. </ bean >
  9. </ mvc:message-converters >
  10. </ mvc:annotation-driven >

通过以上过程,我们已经完成了一个基于Spring MVC的REST框架,只不过该框架还非常单薄,还缺乏很多关键性特性,尤其是异常处理。

4.3 处理异常行为

在Spring MVC中,我们可以使用AOP技术,编写一个全局的异常处理切面类,用它来统一处理所有的异常行为,在Spring 3.2中才开始提供。使用法很简
单,只需定义一个类,并通过@ControllerAdvice注解将其标注即可,同时需要使用@ResponseBody注解表示返回值可序列化为JSON字符串。
代码如下:

[java] view plain copy

  1. @ControllerAdvice
  2. @ResponseBody
  3. public class ExceptionAdvice {
    1. /**
    • 400 - Bad Request
  4. */
  5. @ResponseStatus (HttpStatus.BAD_REQUEST)
  6. @ExceptionHandler (HttpMessageNotReadableException. class )
  7. public Response handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
  8. logger.error( ”参数解析失败” , e);
  9. return new Response().failure( “could_not_read_json” );
  10. }
    1. /**
    • 405 - Method Not Allowed
  11. */
  12. @ResponseStatus (HttpStatus.METHOD_NOT_ALLOWED)
  13. @ExceptionHandler (HttpRequestMethodNotSupportedException. class )
  14. public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
  15. logger.error( ”不支持当前请求方法” , e);
  16. return new Response().failure( “request_method_not_supported” );
  17. }
    1. /**
    • 415 - Unsupported Media Type
  18. */
  19. @ResponseStatus (HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  20. @ExceptionHandler (HttpMediaTypeNotSupportedException. class )
  21. public Response handleHttpMediaTypeNotSupportedException(Exception e) {
  22. logger.error( ”不支持当前媒体类型” , e);
  23. return new Response().failure( “content_type_not_supported” );
  24. }
    1. /**
    • 500 - Internal Server Error
  25. */
  26. @ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR)
  27. @ExceptionHandler (Exception. class )
  28. public Response handleException(Exception e) {
  29. logger.error( ”服务运行异常” , e);
  30. return new Response().failure(e.getMessage());
  31. }
  32. }

可见,在ExceptionAdvice类中包含一系列的异常处理方法,每个方法都通过@ResponseStatus注解定义了响应状态码,此外还通过@Excep
tionHandler注解指定了具体需要拦截的异常类。以上过程只是包含了一部分的异常情况,若需处理其它异常,可添加方法具体的方法。需要注意的是,在运行时从上
往下依次调用每个异常处理方法,匹配当前异常类型是否与@ExceptionHandler注解所定义的异常相匹配,若匹配,则执行该方法,同时忽略后续所有的异常处
理方法,最终会返回经JSON序列化后的Response对象。

4.4 支持参数验证

我们回到上文所提到的示例,这里处理一个普通的POST请求,代码如下:

[java] view plain copy

  1. @RestController
  2. public class AdvertiserController {
    1. @RequestMapping (value = “/advertiser” , method = RequestMethod.POST)
  3. public Response createAdvertiser( @RequestBody AdvertiserParam advertiserParam) {
  4. }
  5. }

其中,AdvertiserParam参数包含若干属性,通过以下类结构可见,它是一个传统的POJO:

[java] view plain copy

  1. public class AdvertiserParam {
    1. private String advertiserName;
    1. private String description;
    1. // 省略 getter/setter 方法
  2. }

如果业务上需要确保AdvertiserParam对象的advertiserName属性必填,如何实现呢?

若将这类参数验证的代码写死在Controller中,势必会与正常的业务逻辑搅在一起,导致责任不够单一,违背于“单一责任原则”。建议将其参数验证行为从Cont
roller中剥离出来,放到另外的类中,这里仅提供一个@Valid注解来定义AdvertiserParam参数,并在AdvertiserParam类中通过@
NotEmpty注解来定义advertiserName属性,就像下面这样:

[java] view plain copy

  1. @RestController
  2. public class AdvertiserController {
    1. @RequestMapping (value = “/advertiser” , method = RequestMethod.POST)
  3. public Response createAdvertiser( @RequestBody @Valid AdvertiserParam advertiserParam) {
  4. }
  5. }
    1. public class AdvertiserParam {
    1. @NotEmpty
  6. private String advertiserName;
    1. private String description;
    1. // 省略 getter/setter 方法
  7. }

这里的@Valid注解实际上是Validation Bean规范提供的注解,该规范已由Hibernate
Validator框架实现,因此需要添加以下Maven依赖到pom.xml文件中:

[xml] view plain copy

  1. < dependency >
  2. < groupId > org.hibernate </ groupId >
  3. < artifactId > hibernate-validator </ artifactId >
  4. < version > ${hibernate-validator.version} </ version >
  5. </ dependency >

需要注意的是,Hibernate Validator与Hibernate没有任何依赖关系,唯一有联系的只是都属于JBoss公司的开源项目而已。

要实现@NotEmpty注解的功能,我们需要做以下几件事情。

首先,定义一个@NotEmpty注解类,代码如下:

[java] view plain copy

  1. @Documented
  2. @Target ({ElementType.FIELD, ElementType.PARAMETER})
  3. @Retention (RetentionPolicy.RUNTIME)
  4. @Constraint (validatedBy = NotEmptyValidator. class )
  5. public @interface NotEmpty {
    1. String message() default “not_empty” ;
    1. Class<?>[] groups() default {};
    1. Class<? extends Payload>[] payload() default {};
  6. }

以上注解类必须包含message、groups、payload三个属性,因为这是规范所要求的,此外,需要通过@Constraint注解指定一个验证器类,这里
对应的是NotEmptyValidator,其代码如下:

[java] view plain copy

  1. public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> {
    1. @Override
  2. public void initialize(NotEmpty constraintAnnotation) {
  3. }
    1. @Override
  4. public boolean isValid(String value, ConstraintValidatorContext context) {
  5. return StringUtil.isNotEmpty(value);
  6. }
  7. }

以上验证器类实现了ConstraintValidator接口,并在该接口的isValid( )方法中完成了具体的参数验证逻辑。需要注意的是,实现接口时需要指
定泛型,第一个参数表示验证注解类型(NotEmpty),第二个参数表示需要验证的参数类型(String)。

然后,我们需要在Spring配置文件中开启该特性,需添加如下配置:

[xml] view plain copy

  1. < bean class = “org.springframework.validation.beanvalidation.MethodValidationPostProcessor” />

最后,需要在全局异常处理类中添加参数验证处理方法,代码如下:

[java] view plain copy

  1. @ControllerAdvice
  2. @ResponseBody
  3. public class ExceptionAdvice {
    1. /**
    • 400 - Bad Request
  4. */
  5. @ResponseStatus (HttpStatus.BAD_REQUEST)
  6. @ExceptionHandler (ValidationException. class )
  7. public Response handleValidationException(ValidationException e) {
  8. logger.error( ”参数验证失败” , e);
  9. return new Response().failure( “validation_exception” );
  10. }
  11. }

至此,REST框架已集成了Bean Validation特性,我们可以使用各种注解来完成所需的参数验证行为了。

看似该框架可以在本地成功跑起来,整个架构包含两个应用,前端应用提供纯静态的HTML页面,后端应用发布REST
API,前端需要通过AJAX调用后端发布的REST
API,然而AJAX是不支持跨域访问的,也就是说,前后端两个应用必须在同一个域名下才能访问。这是非常严重的技术障碍,一定需要找到解决方案。

4.5 解决跨域问题

比如,前端应用为静态站点且部署在http://web.xxx.com域下,后端应用发布REST API并部署在http://api.xxx.com域下,如何
使前端应用通过AJAX跨域访问后端应用呢?这需要使用到CORS技术来实现,这也是目前最好的解决方案了。

[CORS全称为Cross Origin Resource
Sharing(跨域资源共享),服务端只需添加相关响应头信息,即可实现客户端发出AJAX跨域请求。]

CORS技术非常简单,易于实现,目前绝大多数浏览器均已支持该技术(IE8浏览器也支持了),服务端可通过任何编程语言来实现,只要能将CORS响应头写入resp
onse对象中即可。

下面我们继续扩展REST框架,通过CORS技术实现AJAX跨域访问。

首先,我们需要编写一个Filter,用于过滤所有的HTTP请求,并将CORS响应头写入response对象中,代码如下:

[java] view plain copy

  1. public class CorsFilter implements Filter {
    1. private String allowOrigin;
  2. private String allowMethods;
  3. private String allowCredentials;
  4. private String allowHeaders;
  5. private String exposeHeaders;
    1. @Override
  6. public void init(FilterConfig filterConfig) throws ServletException {
  7. allowOrigin = filterConfig.getInitParameter( ”allowOrigin” );
  8. allowMethods = filterConfig.getInitParameter( ”allowMethods” );
  9. allowCredentials = filterConfig.getInitParameter( ”allowCredentials” );
  10. allowHeaders = filterConfig.getInitParameter( ”allowHeaders” );
  11. exposeHeaders = filterConfig.getInitParameter( ”exposeHeaders” );
  12. }
    1. @Override
  13. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  14. HttpServletRequest request = (HttpServletRequest) req;
  15. HttpServletResponse response = (HttpServletResponse) res;
  16. if (StringUtil.isNotEmpty(allowOrigin)) {
  17. List allowOriginList = Arrays.asList(allowOrigin.split( ”,” ));
  18. if (CollectionUtil.isNotEmpty(allowOriginList)) {
  19. String currentOrigin = request.getHeader( ”Origin” );
  20. if (allowOriginList.contains(currentOrigin)) {
  21. response.setHeader( ”Access-Control-Allow-Origin” , currentOrigin);
  22. }
  23. }
  24. }
  25. if (StringUtil.isNotEmpty(allowMethods)) {
  26. response.setHeader( ”Access-Control-Allow-Methods” , allowMethods);
  27. }
  28. if (StringUtil.isNotEmpty(allowCredentials)) {
  29. response.setHeader( ”Access-Control-Allow-Credentials” , allowCredentials);
  30. }
  31. if (StringUtil.isNotEmpty(allowHeaders)) {
  32. response.setHeader( ”Access-Control-Allow-Headers” , allowHeaders);
  33. }
  34. if (StringUtil.isNotEmpty(exposeHeaders)) {
  35. response.setHeader( ”Access-Control-Expose-Headers” , exposeHeaders);
  36. }
  37. chain.doFilter(req, res);
  38. }
    1. @Override
  39. public void destroy() {
  40. }
  41. }

以上CorsFilter将从web.xml中读取相关Filter初始化参数,并将在处理HTTP请求时将这些参数写入对应的CORS响应头中,下面大致描述一下这
些CORS响应头的意义:

  • Access-Control-Allow-Origin:允许访问的客户端域名,例如:http://web.xxx.com,若为*,则表示从任意域都能访问,即不做任何限制。
  • Access-Control-Allow-Methods:允许访问的方法名,多个方法名用逗号分割,例如:GET,POST,PUT,DELETE,OPTIONS。
  • Access-Control-Allow-Credentials:是否允许请求带有验证信息,若要获取客户端域下的cookie时,需要将其设置为true。
  • Access-Control-Allow-Headers:允许服务端访问的客户端请求头,多个请求头用逗号分割,例如:Content-Type。
  • Access-Control-Expose-Headers:允许客户端访问的服务端响应头,多个响应头用逗号分割。

需要注意的是,CORS规范中定义Access-Control-Allow-Origin只允许两种取值,要么为*,要么为具体的域名,也就是说,不支持同时配置多
个域名。为了解决跨多个域的问题,需要在代码中做一些处理,这里将Filter初始化参数作为一个域名的集合(用逗号分隔),只需从当前请求中获取Origin请求头
,就知道是从哪个域中发出的请求,若该请求在以上允许的域名集合中,则将其放入Access-Control-Allow-
Origin响应头,这样跨多个域的问题就轻松解决了。

以下是web.xml中配置CorsFilter的方法:

[xml] view plain copy

  1. < filter >
  2. < filter-name > corsFilter </ filter-name >
  3. < filter-class > com.xxx.api.cors.CorsFilter </ filter-class >
  4. < init-param >
  5. < param-name > allowOrigin </ param-name >
  6. < param-value > http://web.xxx.com </ param-value >
  7. </ init-param >
  8. < init-param >
  9. < param-name > allowMethods </ param-name >
  10. < param-value > GET,POST,PUT,DELETE,OPTIONS </ param-value >
  11. </ init-param >
  12. < init-param >
  13. < param-name > allowCredentials </ param-name >
  14. < param-value > true </ param-value >
  15. </ init-param >
  16. < init-param >
  17. < param-name > allowHeaders </ param-name >
  18. < param-value > Content-Type </ param-value >
  19. </ init-param >
  20. </ filter >
  21. < filter-mapping >
  22. < filter-name > corsFilter </ filter-name >
  23. < url-pattern > /* </ url-pattern >
  24. </ filter-mapping >

完成以上过程即可实现AJAX跨域功能了,但似乎还存在另外一个问题,由于REST是无状态的,后端应用发布的REST
API可在用户未登录的情况下被任意调用,这显然是不安全的,如何解决这个问题呢?我们需要为REST请求提供安全机制。

4.6 提供安全机制

解决REST安全调用问题,可以做得很复杂,也可以做得特简单,可按照以下过程提供REST安全机制:

  1. 当用户登录成功后,在服务端生成一个token,并将其放入内存中(可放入JVM或Redis中),同时将该token返回到客户端。
  2. 在客户端中将返回的token写入cookie中,并且每次请求时都将token随请求头一起发送到服务端。
  3. 提供一个AOP切面,用于拦截所有的Controller方法,在切面中判断token的有效性。
  4. 当登出时,只需清理掉cookie中的token即可,服务端token可设置过期时间,使其自行移除。

首先,我们需要定义一个用于管理token的接口,包括创建token与检查token有效性的功能。代码如下:

[java] view plain copy

  1. public interface TokenManager {
    1. String createToken(String username);
    1. boolean checkToken(String token);
  2. }

然后,我们可提供一个简单的TokenManager实现类,将token存储到JVM内存中。代码如下:

[java] view plain copy

  1. public class DefaultTokenManager implements TokenManager {
    1. private static Map<String, String> tokenMap = new ConcurrentHashMap<>();
    1. @Override
  2. public String createToken(String username) {
  3. String token = CodecUtil.createUUID();
  4. tokenMap.put(token, username);
  5. return token;
  6. }
    1. @Override
  7. public boolean checkToken(String token) {
  8. return !StringUtil.isEmpty(token) && tokenMap.containsKey(token);
  9. }
  10. }

需要注意的是,如果需要做到分布式集群,建议基于Redis提供一个实现类,将token存储到Redis中,并利用Redis与生俱来的特性,做到token的分布
式一致性。

然后,我们可以基于Spring
AOP写一个切面类,用于拦截Controller类的方法,并从请求头中获取token,最后对token有效性进行判断。代码如下:

[java] view plain copy

  1. public class SecurityAspect {
    1. private static final String DEFAULT_TOKEN_NAME = “X-Token” ;
    1. private TokenManager tokenManager;
  2. private String tokenName;
    1. public void setTokenManager(TokenManager tokenManager) {
  3. this .tokenManager = tokenManager;
  4. }
    1. public void setTokenName(String tokenName) {
  5. if (StringUtil.isEmpty(tokenName)) {
  6. tokenName = DEFAULT_TOKEN_NAME;
  7. }
  8. this .tokenName = tokenName;
  9. }
    1. public Object execute(ProceedingJoinPoint pjp) throws Throwable {
  10. // 从切点上获取目标方法
  11. MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
  12. Method method = methodSignature.getMethod();
  13. // 若目标方法忽略了安全性检查,则直接调用目标方法
  14. if (method.isAnnotationPresent(IgnoreSecurity. class )) {
  15. return pjp.proceed();
  16. }
  17. // 从 request header 中获取当前 token
  18. String token = WebContext.getRequest().getHeader(tokenName);
  19. // 检查 token 有效性
  20. if (!tokenManager.checkToken(token)) {
  21. String message = String.format( ”token [%s] is invalid” , token);
  22. throw new TokenException(message);
  23. }
  24. // 调用目标方法
  25. return pjp.proceed();
  26. }
  27. }

若要使SecurityAspect生效,则需要添加如下Spring 配置:

[xml] view plain copy

  1. < bean id = “securityAspect” class = “com.xxx.api.security.SecurityAspect” >
  2. < property name = “tokenManager” ref = “tokenManager” />
  3. < property name = “tokenName” value = “X-Token” />
  4. </ bean >
    1. < aop:config >
  5. < aop:aspect ref = “securityAspect” >
  6. < aop:around method = “execute” pointcut = “@annotation(org.springframework.web.bind.annotation.RequestMapping)” />
  7. </ aop:aspect >
  8. </ aop:config >

最后,别忘了在web.xml中添加允许的X-Token响应头,配置如下:

[xml] view plain copy

  1. < init-param >
  2. < param-name > allowHeaders </ param-name >
  3. < param-value > Content-Type,X-Token </ param-value >
  4. </ init-param >

总结

本文从经典的MVC模式开始,对MVC模式是什么以及该模式存在的不足进行了简述。然后引出了如何对MVC模式的改良,让其转变为前后端分离架构,以及解释了为何要进
行前后端分离。最后通过REST服务将前后端进行解耦,并提供了一款基于Java的REST框架的主要实现过程,尤其是需要注意的核心技术问题及其解决方案。希望本文
对正在探索前后端分离的读者们有所帮助,期待与大家共同探讨。

评论

从Redis分区的优缺点来看适合的应用场景

正文

Redis Partitioning即Redis分区,简单的说就是将数据分布到不同的redis实例中,因此对于每个redis实例所存储的内容仅仅是所有内容的
一个子集。分区(Partitioning)不仅仅是Redis中的概念,几乎是所有数据存储系统都会涉及到的概念,这篇文章将会在理解分区基本概念的基础之上进一步
了解Redis对分区的支持。

一、我们为什么要分区

我们为什么要分区?分区的动机是什么?通常来说,Redis分区的好处大致有如下两个方面:

性能的提升,单机Redis的网络I/O能力和计算资源是有限的,将请求分散到多台机器,充分利用多台机器的计算能力可网络带宽,有助于提高Redis总体的服务能力

存储的横向扩展,即使Redis的服务能力能够满足应用需求,但是随着存储数据的增加,单台机器受限于机器本身的存储容量,将数据分散到多台机器上存储使得Redis
服务可以横向扩展。

总的来说,分区使得我们本来受限于单台计算机硬件资源的问题不再是问题,存储不够?计算资源不够?带宽不够?我们都可以通过增加机器来解决这些问题。

二、Redis分区基础

实际应用中有很多分区的具体策略,举个例子,假设我们已经有了一组四个Redis实例分别为R0、R1、R2、R3,另外我们有一批代表用户的键,如:user:1,
user:2,……等等,其中“user:”后面的数字代表的是用户的ID,我们要做的事情是把这些键分散存储在这四个不同的Redis实例上。怎么做呢?最简单的一
种方式是范围分区(range partitioning),下面我们来看看基于范围分区怎么做。

范围分区
所谓范围分区,就是将一个范围内的key都映射到同一个Redis实例中,加入数据集还是上面提到的用户数据,具体做法如下:

我们可以将用户ID从0到10000的用户数据映射到R0实例,而将用户ID从10001到20000的对象映射到R1实例,依次类推。

这种方法虽然简单,但是在实际应用中是很有效的,不过还是有问题:

我们需要一张表,这张表用来存储用户ID范围到Redis实例的映射关系,比如用户ID0-10000的是映射到R0实例……。

我们不仅需要对这张表进行维护,而且对于每种对象类型我们都需要一个这样的表,比如我们当前存储的是用户信息,如果存储的是订单信息,我们就需要再建一张映射关系表。

如果我们想要存储的数据的key并不能按照范围划分怎么办,比如我们的key是一组uuid,这个时候就不好用范围分区了。

因此,在实际应用中,范围分区并不是很好的选择,不用担心,我们还有更好的方法,接下来认识下哈希分区。

哈希分区
哈希分区跟范围分区相比一个明显的优点是哈希分区适合任何形式的key,而不像范围分区一样需要key的形式为object_name:,而且分区方法也很简
单,一个公式就可以表达:

id=hash(key)%N

其中id代表Redis实例的编号,公式描述的是首先根据key和一个hash函数(如crc32函数)计算出一个数值型的值。接着上面的例子,我们的第一个要处理的
key是user:1,hash(user:1)的结果是93024922。

然后哈希结果进行取模,取模的目的是计算出一个介于0到3之间的值,因此这个值才可以被映射到我们的一台Redis实例上面。比如93024922%4结果是2,我们
就会知道foobar将要被存储在R2上面。

当然除了上面提到的两种分区方法,还有很多其他的方法。比如一种从哈希分区演进而来的consistent
hashing分区,相信信息可以参考我的另一篇文章《memcached分布式实现原理》,其已经被redis client和proxies实现了。

三、不同的分区实现

分区可以在redis软件栈的不同部分被实现,我们来看看下面几种:

客户端实现

客户端实现即key在redis客户端就决定了要被存储在那台Redis实例中,见下图:

客户端实现分区示意图

代理实现

代理实现即客户端将请求发往代理服务器,代理服务器实现了Redis协议,因此代理服务器可以代理客户端和Redis服务器通信。代理服务器通过配置的分区schem
a来将客户端的请求转发到正确的Redis实例中,同时将反馈消息返回给客户端。代理实现Redis分区示意图如下:


代理实现Redis分区示意图

Redis和Memcached代理Twemoroxy都实现了代理分区。

查询路由

查询路由是Redis Cluster实现的一种Redis分区方式:

查询路由Redis分区示意图

查询路由的过程中,我们可以将查询请求随机的发送到任意一个Redis实例,这个Redis实例负责将请求转发至正确的Redis实例中。Redis集群实现了一个通
过和客户端协作的hybrid来做查询路由。

四、Redis分区的缺点

尽管Redis分区到现在为止,so far so
good,但是Redis分区有一些致命的缺点,这导致一些Redis功能在分区的环境下并不能很好地工作,我们来看看:

多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中。

多键的Redis事务是不被支持的。

分区的最小粒度是键,因此我们不能将关联到一个键的很大的数据集映射到不同的实例。

当应用分区的时候,数据的处理是非常复杂的,比如我们需要处理多个rdb/aof文件,将分布在不同实例的文件聚集到一起备份。

添加和删除机器是很复杂的,例如Redis集群支持几乎运行时透明的因为增加或减少机器而需要做的rebalancing,然而像客户端和代理分区这种方式是不支持这
种功能的。

既然有问题,那么就需要解决方案,这个时候Pre-sharding来了,后面我们会介绍Pre-Sharding。

五、持久存储用还是缓存

尽管数据分区对于Redis来说无论是数据持久化存储还是缓存,在概念上都是一样的,然而对于数据持久化存储还是有一个很大的限制。当我们使用Redis来作为持久化
存储的时候,每一个key必须一直被映射到同一个Redis实例。而当Redis被当做缓存使用的时候,对于这个key,如果一个实例不能用了,这个key还可以被映
射到其他的实例中。

Consistent hashing实现通常使得当一个key被映射到的实例不能用的时候将这个key映射到其他实例成为可能。类似,如果增加了一台机器,一部分的
key将会被映射到这台新的机器上,我们需要了解的两点如下:

如果Redis被用来当做缓存,且要求容易增加或删除机器,使用consistent hashing是非常简单的。

如果Redis被用来当做(持久)存储,一个固定的key到实例的映射是需要的,因此我们不能够再灵活的添加或删除机器。否则,我们需要在增加或删除机器的时候系统能
够rebalace,当前Redis Cluster已经支持。

六、Pre-Sharding

通过上面的介绍,我们知道Redis分区应用起来是有问题的,除非我们只是使用Redis当做缓存,否则对于增加机器或删除机器是非常麻烦的。

然而,通常我们Redis容量变动在实际应用中是非常常见的,比如今天我需要10台Redis机器,明天可能就需要50台机器了。

鉴于Redis是很轻量级的服务(每个实例仅仅占用1M),对于上面的问题一种简单的解决办法是:

我们可以开启多个Redis实例,尽管是一台物理机器,我们在刚开始的时候也可以开启多个实例。我们可以从中选择一些实例,比如32或64个实例来作为我们的工作集群
。当一台物理机器存储不够的时候,我们可以将一般的实例移动到我们的第二台物理机上,依次类对,我们可以保证集群中Redis的实例数不变,又可以达到扩充机器的目的

怎么移动Redis实例呢?当需要将Redis实例移动到独立的机器上的时候,我们可以通过下面步骤实现:

在新的物理机上启动一个新的Redis实例。

将新的物理机作为要移动的那台的slave机器。

停止客户端。

更新将要被移动的那台Redis实例的IP地址。

对于slave机器发送SLAVEOF ON ONE命令。

使用新的IP启动Redis客户端。

关闭不再使用的那个Redis实例。

七、总结

这篇文章在理解Redis分区概念的基础之上又介绍了Redis分区常见的几种实现方式及原理,最后根据实现中遇到的问题引入了Pre-Sharding解决方案。

参考文献

《Redis官方文档》

评论

程序猿技能表

程序猿技能表:分别是Java、C/C++、Android、iOS、PHP、前端、Python、.NET/C# JAVA PYTHON 前端 PHP

C++ ios

android

.net

评论

sqlserver汉字转拼音

/* ————————————————————-

函数 : fn_GetPinyin

描述 : 汉字转拼音 ( 无数据表版 )

使用 : dbo.fn_GetPinyin(‘ 中华人民共和国 ‘) = zhonghuarenmingongheguo

作者 : 流香羽 ( 改编: Tony)

博客 : http://hi.baidu.com/ 流香羽

-———————————————————— */

-- 创建函数

IF OBJECT_ID ( ‘[fn_GetPinyin]’ ) IS NOT NULL

DROP FUNCTION [fn_GetPinyin]

GO

create function [dbo] . [fn_GetPinyin] ( @words nvarchar ( 2000 ))

returns varchar ( 8000 )

as

begin

declare @word nchar ( 1 )

declare @pinyin varchar ( 8000 )

declare @i int

declare @words_len int

declare @unicode int

set @i = 1

set @words = ltrim ( rtrim ( @words ))

set @words_len = len ( @words )

while ( @i <= @words_len ) -- 循环取字符

begin

set @word = substring ( @words , @i , 1 )

set @unicode = unicode ( @word )

set @pinyin = ISNULL ( @pinyin + SPACE ( 1 ), ‘’ )+

( case when unicode ( @word ) between 19968 and 19968 + 20901
then

( select top 1 py from (

select ‘a’ as py , N ‘ 厑 ‘ as word

union all select ‘ai’ , N ‘ 靉 ‘

union all select ‘an’ , N ‘ 黯 ‘

union all select ‘ang’ , N ‘ 醠 ‘

union all select ‘ao’ , N ‘ 驁 ‘

union all select ‘ba’ , N ‘ 欛 ‘

union all select ‘bai’ , N ‘ 瓸 ‘ -- 韛兡瓸

union all select ‘ban’ , N ‘ 瓣 ‘

union all select ‘bang’ , N ‘ 鎊 ‘

union all select ‘bao’ , N ‘ 鑤 ‘

union all select ‘bei’ , N ‘ 鐾 ‘

union all select ‘ben’ , N ‘ 輽 ‘

union all select ‘beng’ , N ‘ 鏰 ‘

union all select ‘bi’ , N ‘ 鼊 ‘

union all select ‘bian’ , N ‘ 變 ‘

union all select ‘biao’ , N ‘ 鰾 ‘

union all select ‘bie’ , N ‘ 彆 ‘

union all select ‘bin’ , N ‘ 鬢 ‘

union all select ‘bing’ , N ‘ 靐 ‘

union all select ‘bo’ , N ‘ 蔔 ‘

union all select ‘bu’ , N ‘ 簿 ‘

union all select ‘ca’ , N ‘ 囃 ‘

union all select ‘cai’ , N ‘ 乲 ‘ -- 縩乲

union all select ‘can’ , N ‘ 爘 ‘

union all select ‘cang’ , N ‘ 賶 ‘

union all select ‘cao’ , N ‘ 鼜 ‘

union all select ‘ce’ , N ‘ 簎 ‘

union all select ‘cen’ , N ‘ 笒 ‘

union all select ‘ceng’ , N ‘ 乽 ‘ -- 硛硳岾猠乽

union all select ‘cha’ , N ‘ 詫 ‘

union all select ‘chai’ , N ‘ 囆 ‘

union all select ‘chan’ , N ‘ 顫 ‘

union all select ‘chang’ , N ‘ 韔 ‘

union all select ‘chao’ , N ‘ 觘 ‘

union all select ‘che’ , N ‘ 爡 ‘

union all select ‘chen’ , N ‘ 讖 ‘

union all select ‘cheng’ , N ‘ 秤 ‘

union all select ‘chi’ , N ‘ 鷘 ‘

union all select ‘chong’ , N ‘ 銃 ‘

union all select ‘chou’ , N ‘ 殠 ‘

union all select ‘chu’ , N ‘ 矗 ‘

union all select ‘chuai’ , N ‘ 踹 ‘

union all select ‘chuan’ , N ‘ 鶨 ‘

union all select ‘chuang’ , N ‘ 愴 ‘

union all select ‘chui’ , N ‘ 顀 ‘

union all select ‘chun’ , N ‘ 蠢 ‘

union all select ‘chuo’ , N ‘ 縒 ‘

union all select ‘ci’ , N ‘ 嗭 ‘ -- 賜嗭

union all select ‘cong’ , N ‘ 謥 ‘

union all select ‘cou’ , N ‘ 輳 ‘

union all select ‘cu’ , N ‘ 顣 ‘

union all select ‘cuan’ , N ‘ 爨 ‘

union all select ‘cui’ , N ‘ 臎 ‘

union all select ‘cun’ , N ‘ 籿 ‘

union all select ‘cuo’ , N ‘ 錯 ‘

union all select ‘da’ , N ‘ 橽 ‘

union all select ‘dai’ , N ‘ 靆 ‘

union all select ‘dan’ , N ‘ 饏 ‘

union all select ‘dang’ , N ‘ 闣 ‘

union all select ‘dao’ , N ‘ 纛 ‘

union all select ‘de’ , N ‘ 的 ‘

union all select ‘den’ , N ‘ 扽 ‘

union all select ‘deng’ , N ‘ 鐙 ‘

union all select ‘di’ , N ‘ 螮 ‘

union all select ‘dia’ , N ‘ 嗲 ‘

union all select ‘dian’ , N ‘ 驔 ‘

union all select ‘diao’ , N ‘ 鑃 ‘

union all select ‘die’ , N ‘ 嚸 ‘ -- 眰嚸

union all select ‘ding’ , N ‘ 顁 ‘

union all select ‘diu’ , N ‘ 銩 ‘

union all select ‘dong’ , N ‘ 霘 ‘

union all select ‘dou’ , N ‘ 鬭 ‘

union all select ‘du’ , N ‘ 蠹 ‘

union all select ‘duan’ , N ‘ 叾 ‘ -- 籪叾

union all select ‘dui’ , N ‘ 譵 ‘

union all select ‘dun’ , N ‘ 踲 ‘

union all select ‘duo’ , N ‘ 鵽 ‘

union all select ‘e’ , N ‘ 鱷 ‘

union all select ‘en’ , N ‘ 摁 ‘

union all select ‘eng’ , N ‘ 鞥 ‘

union all select ‘er’ , N ‘ 樲 ‘

union all select ‘fa’ , N ‘ 髮 ‘

union all select ‘fan’ , N ‘ 瀪 ‘

union all select ‘fang’ , N ‘ 放 ‘

union all select ‘fei’ , N ‘ 靅 ‘

union all select ‘fen’ , N ‘ 鱝 ‘

union all select ‘feng’ , N ‘ 覅 ‘

union all select ‘fo’ , N ‘ 梻 ‘

union all select ‘fou’ , N ‘ 鴀 ‘

union all select ‘fu’ , N ‘ 猤 ‘ -- 鰒猤

union all select ‘ga’ , N ‘ 魀 ‘

union all select ‘gai’ , N ‘ 瓂 ‘

union all select ‘gan’ , N ‘ 灨 ‘

union all select ‘gang’ , N ‘ 戇 ‘

union all select ‘gao’ , N ‘ 鋯 ‘

union all select ‘ge’ , N ‘ 獦 ‘

union all select ‘gei’ , N ‘ 給 ‘

union all select ‘gen’ , N ‘ 搄 ‘

union all select ‘geng’ , N ‘ 堩 ‘ -- 亙堩啹喼嗰

union all select ‘gong’ , N ‘ 兣 ‘ -- 熕贑兝兣

union all select ‘gou’ , N ‘ 購 ‘

union all select ‘gu’ , N ‘ 顧 ‘

union all select ‘gua’ , N ‘ 詿 ‘

union all select ‘guai’ , N ‘ 恠 ‘

union all select ‘guan’ , N ‘ 鱹 ‘

union all select ‘guang’ , N ‘ 撗 ‘

union all select ‘gui’ , N ‘ 鱥 ‘

union all select ‘gun’ , N ‘ 謴 ‘

union all select ‘guo’ , N ‘ 腂 ‘

union all select ‘ha’ , N ‘ 哈 ‘

union all select ‘hai’ , N ‘ 饚 ‘

union all select ‘han’ , N ‘ 鶾 ‘

union all select ‘hang’ , N ‘ 沆 ‘

union all select ‘hao’ , N ‘ 兞 ‘

union all select ‘he’ , N ‘ 靏 ‘

union all select ‘hei’ , N ‘ 嬒 ‘

union all select ‘hen’ , N ‘ 恨 ‘

union all select ‘heng’ , N ‘ 堼 ‘ -- 堼囍

union all select ‘hong’ , N ‘ 鬨 ‘

union all select ‘hou’ , N ‘ 鱟 ‘

union all select ‘hu’ , N ‘ 鸌 ‘

union all select ‘hua’ , N ‘ 蘳 ‘

union all select ‘huai’ , N ‘ 蘾 ‘

union all select ‘huan’ , N ‘ 鰀 ‘

union all select ‘huang’ , N ‘ 鎤 ‘

union all select ‘hui’ , N ‘ 顪 ‘

union all select ‘hun’ , N ‘ 諢 ‘

union all select ‘huo’ , N ‘ 夻 ‘

union all select ‘ji’ , N ‘ 驥 ‘

union all select ‘jia’ , N ‘ 嗧 ‘

union all select ‘jian’ , N ‘ 鑳 ‘

union all select ‘jiang’ , N ‘ 謽 ‘

union all select ‘jiao’ , N ‘ 釂 ‘

union all select ‘jie’ , N ‘ 繲 ‘

union all select ‘jin’ , N ‘ 齽 ‘

union all select ‘jing’ , N ‘ 竸 ‘

union all select ‘jiong’ , N ‘ 蘔 ‘

union all select ‘jiu’ , N ‘ 欍 ‘

union all select ‘ju’ , N ‘ 爠 ‘

union all select ‘juan’ , N ‘ 羂 ‘

union all select ‘jue’ , N ‘ 钁 ‘

union all select ‘jun’ , N ‘ 攈 ‘

union all select ‘ka’ , N ‘ 鉲 ‘

union all select ‘kai’ , N ‘ 乫 ‘ -- 鎎乫

union all select ‘kan’ , N ‘ 矙 ‘

union all select ‘kang’ , N ‘ 閌 ‘

union all select ‘kao’ , N ‘ 鯌 ‘

union all select ‘ke’ , N ‘ 騍 ‘

union all select ‘ken’ , N ‘ 褃 ‘

union all select ‘keng’ , N ‘ 鏗 ‘ -- 巪乬唟厼怾

union all select ‘kong’ , N ‘ 廤 ‘

union all select ‘kou’ , N ‘ 鷇 ‘

union all select ‘ku’ , N ‘ 嚳 ‘

union all select ‘kua’ , N ‘ 骻 ‘

union all select ‘kuai’ , N ‘ 鱠 ‘

union all select ‘kuan’ , N ‘ 窾 ‘

union all select ‘kuang’ , N ‘ 鑛 ‘

union all select ‘kui’ , N ‘ 鑎 ‘

union all select ‘kun’ , N ‘ 睏 ‘

union all select ‘kuo’ , N ‘ 穒 ‘

union all select ‘la’ , N ‘ 鞡 ‘

union all select ‘lai’ , N ‘ 籟 ‘

union all select ‘lan’ , N ‘ 糷 ‘

union all select ‘lang’ , N ‘ 唥 ‘

union all select ‘lao’ , N ‘ 軂 ‘

union all select ‘le’ , N ‘ 餎 ‘

union all select ‘lei’ , N ‘ 脷 ‘ -- 嘞脷

union all select ‘leng’ , N ‘ 睖 ‘

union all select ‘li’ , N ‘ 瓈 ‘

union all select ‘lia’ , N ‘ 倆 ‘

union all select ‘lian’ , N ‘ 纞 ‘

union all select ‘liang’ , N ‘ 鍄 ‘

union all select ‘liao’ , N ‘ 瞭 ‘

union all select ‘lie’ , N ‘ 鱲 ‘

union all select ‘lin’ , N ‘ 轥 ‘ -- 轥拎

union all select ‘ling’ , N ‘ 炩 ‘

union all select ‘liu’ , N ‘ 咯 ‘ -- 瓼甅囖咯

union all select ‘long’ , N ‘ 贚 ‘

union all select ‘lou’ , N ‘ 鏤 ‘

union all select ‘lu’ , N ‘ 氇 ‘

union all select ‘lv’ , N ‘ 鑢 ‘

union all select ‘luan’ , N ‘ 亂 ‘

union all select ‘lue’ , N ‘ 擽 ‘

union all select ‘lun’ , N ‘ 論 ‘

union all select ‘luo’ , N ‘ 鱳 ‘

union all select ‘ma’ , N ‘ 嘛 ‘

union all select ‘mai’ , N ‘ 霢 ‘

union all select ‘man’ , N ‘ 蘰 ‘

union all select ‘mang’ , N ‘ 蠎 ‘

union all select ‘mao’ , N ‘ 唜 ‘

union all select ‘me’ , N ‘ 癦 ‘ -- 癦呅

union all select ‘mei’ , N ‘ 嚜 ‘

union all select ‘men’ , N ‘ 們 ‘

union all select ‘meng’ , N ‘ 霥 ‘ -- 霿踎

union all select ‘mi’ , N ‘ 羃 ‘

union all select ‘mian’ , N ‘ 麵 ‘

union all select ‘miao’ , N ‘ 廟 ‘

union all select ‘mie’ , N ‘ 鱴 ‘ -- 鱴瓱

union all select ‘min’ , N ‘ 鰵 ‘

union all select ‘ming’ , N ‘ 詺 ‘

union all select ‘miu’ , N ‘ 謬 ‘

union all select ‘mo’ , N ‘ 耱 ‘ -- 耱乮

union all select ‘mou’ , N ‘ 麰 ‘ -- 麰蟱

union all select ‘mu’ , N ‘ 旀 ‘

union all select ‘na’ , N ‘ 魶 ‘

union all select ‘nai’ , N ‘ 錼 ‘

union all select ‘nan’ , N ‘ 婻 ‘

union all select ‘nang’ , N ‘ 齉 ‘

union all select ‘nao’ , N ‘ 臑 ‘

union all select ‘ne’ , N ‘ 呢 ‘

union all select ‘nei’ , N ‘ 焾 ‘ -- 嫩焾

union all select ‘nen’ , N ‘ 嫩 ‘

union all select ‘neng’ , N ‘ 能 ‘ -- 莻嗯鈪銰啱

union all select ‘ni’ , N ‘ 嬺 ‘

union all select ‘nian’ , N ‘ 艌 ‘

union all select ‘niang’ , N ‘ 釀 ‘

union all select ‘niao’ , N ‘ 脲 ‘

union all select ‘nie’ , N ‘ 钀 ‘

union all select ‘nin’ , N ‘ 拰 ‘

union all select ‘ning’ , N ‘ 濘 ‘

union all select ‘niu’ , N ‘ 靵 ‘

union all select ‘nong’ , N ‘ 齈 ‘

union all select ‘nou’ , N ‘ 譳 ‘

union all select ‘nu’ , N ‘ 搙 ‘

union all select ‘nv’ , N ‘ 衄 ‘

union all select ‘nue’ , N ‘ 瘧 ‘

union all select ‘nuan’ , N ‘ 燶 ‘ -- 硸黁燶郍

union all select ‘nuo’ , N ‘ 桛 ‘

union all select ‘o’ , N ‘ 鞰 ‘ -- 毮夞乯鞰

union all select ‘ou’ , N ‘ 漚 ‘

union all select ‘pa’ , N ‘ 袙 ‘

union all select ‘pai’ , N ‘ 磗 ‘ -- 鎃磗

union all select ‘pan’ , N ‘ 鑻 ‘

union all select ‘pang’ , N ‘ 胖 ‘

union all select ‘pao’ , N ‘ 礮 ‘

union all select ‘pei’ , N ‘ 轡 ‘

union all select ‘pen’ , N ‘ 喯 ‘

union all select ‘peng’ , N ‘ 喸 ‘ -- 浌巼闏乶喸

union all select ‘pi’ , N ‘ 鸊 ‘

union all select ‘pian’ , N ‘ 騙 ‘

union all select ‘piao’ , N ‘ 慓 ‘

union all select ‘pie’ , N ‘ 嫳 ‘

union all select ‘pin’ , N ‘ 聘 ‘

union all select ‘ping’ , N ‘ 蘋 ‘

union all select ‘po’ , N ‘ 魄 ‘

union all select ‘pou’ , N ‘ 哛 ‘ -- 兺哛

union all select ‘pu’ , N ‘ 曝 ‘

union all select ‘qi’ , N ‘ 蟿 ‘

union all select ‘qia’ , N ‘ 髂 ‘

union all select ‘qian’ , N ‘ 縴 ‘

union all select ‘qiang’ , N ‘ 瓩 ‘ -- 羻兛瓩

union all select ‘qiao’ , N ‘ 躈 ‘

union all select ‘qie’ , N ‘ 籡 ‘

union all select ‘qin’ , N ‘ 藽 ‘

union all select ‘qing’ , N ‘ 櫦 ‘

union all select ‘qiong’ , N ‘ 瓗 ‘

union all select ‘qiu’ , N ‘ 糗 ‘

union all select ‘qu’ , N ‘ 覻 ‘

union all select ‘quan’ , N ‘ 勸 ‘

union all select ‘que’ , N ‘ 礭 ‘

union all select ‘qun’ , N ‘ 囕 ‘

union all select ‘ran’ , N ‘ 橪 ‘

union all select ‘rang’ , N ‘ 讓 ‘

union all select ‘rao’ , N ‘ 繞 ‘

union all select ‘re’ , N ‘ 熱 ‘

union all select ‘ren’ , N ‘ 餁 ‘

union all select ‘reng’ , N ‘ 陾 ‘

union all select ‘ri’ , N ‘ 馹 ‘

union all select ‘rong’ , N ‘ 穃 ‘

union all select ‘rou’ , N ‘ 嶿 ‘

union all select ‘ru’ , N ‘ 擩 ‘

union all select ‘ruan’ , N ‘ 礝 ‘

union all select ‘rui’ , N ‘ 壡 ‘

union all select ‘run’ , N ‘ 橍 ‘ -- 橍挼

union all select ‘ruo’ , N ‘ 鶸 ‘

union all select ‘sa’ , N ‘ 栍 ‘ -- 櫒栍

union all select ‘sai’ , N ‘ 虄 ‘ -- 簺虄

union all select ‘san’ , N ‘ 閐 ‘

union all select ‘sang’ , N ‘ 喪 ‘

union all select ‘sao’ , N ‘ 髞 ‘

union all select ‘se’ , N ‘ 飋 ‘ -- 裇聓

union all select ‘sen’ , N ‘ 篸 ‘

union all select ‘seng’ , N ‘ 縇 ‘ -- 閪縇

union all select ‘sha’ , N ‘ 霎 ‘

union all select ‘shai’ , N ‘ 曬 ‘

union all select ‘shan’ , N ‘ 鱔 ‘

union all select ‘shang’ , N ‘ 緔 ‘

union all select ‘shao’ , N ‘ 潲 ‘

union all select ‘she’ , N ‘ 欇 ‘

union all select ‘shen’ , N ‘ 瘮 ‘

union all select ‘sheng’ , N ‘ 賸 ‘

union all select ‘shi’ , N ‘ 瓧 ‘ -- 鰘齛兙瓧

union all select ‘shou’ , N ‘ 鏉 ‘

union all select ‘shu’ , N ‘ 虪 ‘

union all select ‘shua’ , N ‘ 誜 ‘

union all select ‘shuai’ , N ‘ 卛 ‘

union all select ‘shuan’ , N ‘ 腨 ‘

union all select ‘shuang’ , N ‘ 灀 ‘

union all select ‘shui’ , N ‘ 睡 ‘

union all select ‘shun’ , N ‘ 鬊 ‘

union all select ‘shuo’ , N ‘ 鑠 ‘

union all select ‘si’ , N ‘ 乺 ‘ -- 瀃螦乺

union all select ‘song’ , N ‘ 鎹 ‘

union all select ‘sou’ , N ‘ 瘶 ‘

union all select ‘su’ , N ‘ 鷫 ‘

union all select ‘suan’ , N ‘ 算 ‘

union all select ‘sui’ , N ‘ 鐩 ‘

union all select ‘sun’ , N ‘ 潠 ‘

union all select ‘suo’ , N ‘ 蜶 ‘

union all select ‘ta’ , N ‘ 襨 ‘ -- 躢襨

union all select ‘tai’ , N ‘ 燤 ‘

union all select ‘tan’ , N ‘ 賧 ‘

union all select ‘tang’ , N ‘ 燙 ‘

union all select ‘tao’ , N ‘ 畓 ‘ -- 討畓

union all select ‘te’ , N ‘ 蟘 ‘

union all select ‘teng’ , N ‘ 朰 ‘ -- 霯唞朰

union all select ‘ti’ , N ‘ 趯 ‘

union all select ‘tian’ , N ‘ 舚 ‘

union all select ‘tiao’ , N ‘ 糶 ‘

union all select ‘tie’ , N ‘ 餮 ‘

union all select ‘ting’ , N ‘ 乭 ‘ -- 濎乭

union all select ‘tong’ , N ‘ 憅 ‘

union all select ‘tou’ , N ‘ 透 ‘

union all select ‘tu’ , N ‘ 鵵 ‘

union all select ‘tuan’ , N ‘ 褖 ‘

union all select ‘tui’ , N ‘ 駾 ‘

union all select ‘tun’ , N ‘ 坉 ‘

union all select ‘tuo’ , N ‘ 籜 ‘

union all select ‘wa’ , N ‘ 韤 ‘

union all select ‘wai’ , N ‘ 顡 ‘

union all select ‘wan’ , N ‘ 贎 ‘

union all select ‘wang’ , N ‘ 朢 ‘

union all select ‘wei’ , N ‘ 躛 ‘

union all select ‘wen’ , N ‘ 璺 ‘

union all select ‘weng’ , N ‘ 齆 ‘

union all select ‘wo’ , N ‘ 齷 ‘

union all select ‘wu’ , N ‘ 鶩 ‘

union all select ‘xi’ , N ‘ 衋 ‘

union all select ‘xia’ , N ‘ 鏬 ‘

union all select ‘xian’ , N ‘ 鼸 ‘

union all select ‘xiang’ , N ‘ 鱌 ‘

union all select ‘xiao’ , N ‘ 斆 ‘

union all select ‘xie’ , N ‘ 躞 ‘

union all select ‘xin’ , N ‘ 釁 ‘

union all select ‘xing’ , N ‘ 臖 ‘

union all select ‘xiong’ , N ‘ 敻 ‘

union all select ‘xiu’ , N ‘ 齅 ‘

union all select ‘xu’ , N ‘ 蓿 ‘

union all select ‘xuan’ , N ‘ 贙 ‘

union all select ‘xue’ , N ‘ 瀥 ‘

union all select ‘xun’ , N ‘ 鑂 ‘

union all select ‘ya’ , N ‘ 齾 ‘

union all select ‘yan’ , N ‘ 灩 ‘

union all select ‘yang’ , N ‘ 樣 ‘

union all select ‘yao’ , N ‘ 鑰 ‘

union all select ‘ye’ , N ‘ 岃 ‘ -- 鸈膶岃

union all select ‘yi’ , N ‘ 齸 ‘

union all select ‘yin’ , N ‘ 檼 ‘

union all select ‘ying’ , N ‘ 譍 ‘

union all select ‘yo’ , N ‘ 喲 ‘

union all select ‘yong’ , N ‘ 醟 ‘

union all select ‘you’ , N ‘ 鼬 ‘

union all select ‘yu’ , N ‘ 爩 ‘

union all select ‘yuan’ , N ‘ 願 ‘

union all select ‘yue’ , N ‘ 鸙 ‘

union all select ‘yun’ , N ‘ 韻 ‘

union all select ‘za’ , N ‘ 雥 ‘

union all select ‘zai’ , N ‘ 縡 ‘

union all select ‘zan’ , N ‘ 饡 ‘

union all select ‘zang’ , N ‘ 臟 ‘

union all select ‘zao’ , N ‘ 竈 ‘

union all select ‘ze’ , N ‘ 稄 ‘

union all select ‘zei’ , N ‘ 鱡 ‘

union all select ‘zen’ , N ‘ 囎 ‘

union all select ‘zeng’ , N ‘ 贈 ‘

union all select ‘zha’ , N ‘ 醡 ‘

union all select ‘zhai’ , N ‘ 瘵 ‘

union all select ‘zhan’ , N ‘ 驏 ‘

union all select ‘zhang’ , N ‘ 瞕 ‘

union all select ‘zhao’ , N ‘ 羄 ‘

union all select ‘zhe’ , N ‘ 鷓 ‘

union all select ‘zhen’ , N ‘ 黮 ‘

union all select ‘zheng’ , N ‘ 證 ‘

union all select ‘zhi’ , N ‘ 豒 ‘

union all select ‘zhong’ , N ‘ 諥 ‘

union all select ‘zhou’ , N ‘ 驟 ‘

union all select ‘zhu’ , N ‘ 鑄 ‘

union all select ‘zhua’ , N ‘ 爪 ‘

union all select ‘zhuai’ , N ‘ 跩 ‘

union all select ‘zhuan’ , N ‘ 籑 ‘

union all select ‘zhuang’ , N ‘ 戅 ‘

union all select ‘zhui’ , N ‘ 鑆 ‘

union all select ‘zhun’ , N ‘ 稕 ‘

union all select ‘zhuo’ , N ‘ 籱 ‘

union all select ‘zi’ , N ‘ 漬 ‘ -- 漬唨

union all select ‘zong’ , N ‘ 縱 ‘

union all select ‘zou’ , N ‘ 媰 ‘

union all select ‘zu’ , N ‘ 謯 ‘

union all select ‘zuan’ , N ‘ 攥 ‘

union all select ‘zui’ , N ‘ 欈 ‘

union all select ‘zun’ , N ‘ 銌 ‘

union all select ‘zuo’ , N ‘ 咗 ‘ ) t

where word >= @word collate Chinese_PRC_CS_AS_KS_WS

order by word collate Chinese_PRC_CS_AS_KS_WS ASC ) else @word end )

set @i = @i + 1

end

return @pinyin

END

GO

-- 测试示例

SELECT dbo . fn_GetPinyin ( ‘ 欢迎访问叶子的博客 ‘ )

-- 运行结果

/*

huan ying fang wen ye zi de bo ke

*/

评论