当前位置: 首页 > news >正文

南平网站建设app开发工具简单

南平网站建设,app开发工具简单,网站建设费合同,网站建设的流程该怎么确定简介 mybatis-plus是一款mybatis增强工具#xff0c;用于简化开发#xff0c;提高效率。mybatis-plus免去了用户编写sql的麻烦#xff0c;只需要创建好实体类#xff0c;并创建一个继承自BaseMapper的接口#xff0c;mybatis就可以自动生成关于单表的crud。mybatis-plus自…简介 mybatis-plus是一款mybatis增强工具用于简化开发提高效率。mybatis-plus免去了用户编写sql的麻烦只需要创建好实体类并创建一个继承自BaseMapper的接口mybatis就可以自动生成关于单表的crud。mybatis-plus自动做了下划线命名到驼峰命名之间的转换并且会根据实体类中的信息动态地生成sql还支持通过java代码来编写sql。 需要注意的是mybatis plus只可以生成单表查询相关的sql对于多表查询mybatis plus没有什么好的解决方案。 入门案例 这是一个springboot整合mybatis plus的入门案例这几乎是mybatis plus最常见的使用场景了毕竟没人会单独使用mybatis plus。这里只展示mybatis plus相关的代码springboot相关的操作不展示 第一步添加依赖 dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.1/version /dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-extension/artifactIdversion3.5.1/version /dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-annotation/artifactIdversion3.5.1/version /dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-generator/artifactIdversion3.5.1/version /dependency第二步springboot中关于mybatis plus的配置 # 1. 配置mapper.xml文件的位置这是默认配置如果没有这个配置mybatis plus默认会去 # mapper目录下寻找xml配置文件建议还是配置上语义更明确 mybatis-plus.configuration.mapper-locationsclasspath:/mapper/*.xml# 2. 打印mybatis-plus日志的两种方式 # 方式1直接打印到控制台 #mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl# 方式2打印到日志文件中这里配置完之后还需要在log4j2的配置文件中配置自定义Logger logging.level.com.baomidou.mybatisplusDEBUG logging.level.org.wyj.blog.mapperDEBUG mybatis-plus.configuration.log-implorg.apache.ibatis.logging.slf4j.Slf4jImpl# 3. 配置表名的统一前缀比如有些项目组喜欢把表名前缀加 t_视图前缀加 v_使用这个配置就会很方便 mybatis-plus.global-config.db-config.table-prefixt_# 4. 全局主键策略为自增避免多次在单个实体类中声明 mybatis-plus.global-config.db-config.id-typeauto# 数据库连接池等其他组件正常配置第三步mybatis plus的配置类 Configuration MapperScan(org.wyj.mapper) // 配置mapper接口的位置 public class MybatisPlusConfig {// 分页插件Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;} }第四步mapper接口和实体类 编写实体类实体类和数据表是对应的注意这里使用自增IDmybatis-plus默认使用雪花算法生成ID编写mapper接口mapper接口只需要继承BaseMapper接口即可 // mapper接口位于MapperScan注解指定的目录下继承BaseMapper指定实体类作为BaseMapper的泛型参数 public interface UserMapper extends BaseMapperUser { }// 和数据库表对应的实体类 Data public class User {TableId(type IdType.AUTO) // 使用数据库自增IDprivate Long id;private String name;private Integer age;private String email; }第五步编写mapper.xml。案例中是一个单表查询的sql。这个案例其实不太好之所以需要编写xml文件是因为mybatis plus无法处理多表查询对于单表查询即使是复杂的sqlmybatis plus也可以搞定不过这里主要是为了演示一下使用mybatis plus是如何配置mapper.xml文件的位置读者只要知道这一点就好。 ?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 2.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespaceorg.wyj.mapper.UserMapperresultMap idbaseMap typeorg.wyj.entity.Userid columnid propertyid jdbcTypeBIGINT /result columnname propertyname jdbcTypeVARCHAR /result columnage propertyage jdbcTypeVARCHAR /result columnemail propertyemail jdbcTypeVARCHAR //resultMapsql idcolumnid, name, age, email/sql!--根据id列表查询用户列表--select idfindUserByIdList resultMapbaseMapSELECT include refidcolumn /FROM userWHERE id INforeach collectionlist open( close) separator, itemid#{id}/foreach/select /mapper第五步测试针对单表的crud。用户没有编写相关sql使用的是mybatis plus提供的能力 RunWith(SpringRunner.class) SpringBootTest(classes {InitApplication.class}) public class UserMapperTest {Autowiredprivate UserMapper userMapper;// 测试selectList方法Testpublic void test1() {ListUser userList userMapper.selectList(null);Assert.assertEquals(5, userList.size());userList.forEach(System.out::println);}// 测试insert方法Testpublic void test2() {User user new User();user.setName(张三);user.setAge(18);user.setEmail(zs163.com);int i userMapper.insert(user);assert i 1;}// 测试分页插件Testpublic void test3() {IPageUser userIPage userMapper.selectPage(new Page(0, 3), null);ListUser records userIPage.getRecords();assert records.size() 3;records.forEach(System.out::println);}// 测试查询条件构造器查询ID等于6的数据Testpublic void test4() {LambdaQueryWrapperUser queryWrapper new LambdaQueryWrapper();queryWrapper.eq(User::getId, 6);User user userMapper.selectOne(queryWrapper);assert user ! null;System.out.println(user user);} }在测试案例中演示了如何使用mybatis plus来编写sql主要有几点 insert语句只需要提供一个实体类mybatis plus就可以根据实体类生成对应表的insert语句select语句在案例中使用LambdaQueryWrapper可以通过java代码来编写复杂的sql只要熟悉sql这些api相信会很熟练。update、delete语句也类似分页语句分页查询是实际开发中比较复杂的点相较于普通的crud。分页查询内部需要两条sql一条计算总条数一条获取当前分页的数据。使用mybatis plus提供的分页查询能力之前需要先配置分页插件。 总结案例中展示了mybatis的基本使用这是springboot接入mybatis plus需要做的最基本的配置。 基本使用 继承BaseMapper实现单表基本crud的功能 用户编写的Mapper接口只需要继承BaseMapper同时在泛型中指定数据表对应的实体类就可以获得基本的增删改查能力 案例用户编写的UserMapper继承BaseMapper同时在泛型中指定实体类User它定了实体类和数据表的对应关系现在用户就可以使用UserMapper来操作user表了 public interface UserMapper extends BaseMapperUser {ListUser findUserByIdList(Param(list) ListLong list); }Data TableName(user) public class User {private Long id;private String name;private Integer age;private String email; }1、查询一个列表selectList方法 Test public void test1() {ListUser users userMapper.selectList(null);assert users ! null !users.isEmpty(); }2、查询单条数据 selectById根据id查询数据selectOne根据指定条件只查询一条数据例如根据唯一索引来查询某个值。 Test public void test2() {User user userMapper.selectById(1L);assert user ! null; }3、新增数据insert方法mybatis plus会自动进行id回填 Test public void test3() {User user new User();user.setName(abc);user.setAge(20);user.setEmail(aa111.com);int insertNum userMapper.insert(user);assert insertNum 1;assert user.getId() ! null; // id自动回填 }4、修改数据updateById方法这里有一个好习惯就是构建数据时只构建要被修改的字段业务语义更明确。 Test public void test4() {// 准备数据long id 1L;String email bbb111.com;User user new User();user.setId(id);user.setEmail(email);// 修改int updateNum userMapper.updateById(user);assert updateNum 1;// 验证User userFromDB userMapper.selectById(id);assert userFromDB ! null email.equals(userFromDB.getEmail()); }5、删除数据deleteById方法 Test public void test5() {int deleteNum userMapper.deleteById(1L);assert deleteNum 1; }上面的几个案例演示了基本的crud操作只需要继承BaseMapper用户就无需再编写复杂的sql。 构建复杂的查询条件 LambdaQueryWrapper 案例编写一条sql查询年龄大于20岁、有email、并且名称中包含指定字符的用户只需要id、name字段 public ListUser getUsersOver20WithEmailAndNameLike(String nameKeyword) {LambdaQueryWrapperUser queryWrapper new LambdaQueryWrapper();// 选择指定字段queryWrapper.select(User::getId, User::getName, User::getAge, User::getEmail)// 条件1年龄大于20岁.gt(User::getAge, 20)// 条件2email不为空.isNotNull(User::getEmail)// 条件3名称包含指定字符(模糊查询).like(StringUtils.isNotBlank(nameKeyword), User::getName, nameKeyword);return userMapper.selectList(queryWrapper); }在这个方法中通过Java代码来构建sql语句中的查询条件只要熟悉sql的编写相信这个方法不难理解。它指定了要查询的字段指定了查询条件同时还有类似于sql标签的功能比起手动编写sql这种方式的效率可以更高因为手动编写sql如果有一些简单的语法错误要在运行阶段才可以看出来通过这种方式基本避免了这种错误。 构建复杂的更新条件 LambdaUpdateWrapper 案例根据用户的姓名更新邮箱假设用户名称在表中有唯一索引 // 根据用户的姓名更新邮箱 public int updateEmailByName(User user) {// 非空校验if (user null || StringUtils.isBlank(user.getName()) || StringUtils.isBlank(user.getEmail())) {return 0;}// 更新LambdaUpdateWrapperUser wrapper new LambdaUpdateWrapper();wrapper.set(User::getEmail, user.getEmail());wrapper.eq(User::getName, user.getName());return userMapper.update(user, wrapper); }// 测试 Test public void test7() {// 构建数据String name aaa;String email aaa111.com;User user new User();user.setName(name);user.setEmail(email);// 测试int updateNum this.updateEmailByName(user);assert updateNum ! 0;// 验证LambdaQueryWrapperUser wrapper new LambdaQueryWrapper();wrapper.eq(User::getName, name);ListUser users userMapper.selectList(wrapper);assert !users.isEmpty();for (User u : users) {assert email.equals(u.getEmail());} }sql片段 setSql方法 在某些场景下需要数据库中的字段自更新而不是依赖实体类中的值例如版本号加1这个时候就需要指定sql片段 案例 更新表中记录的状态并且版本号加1 LambdaUpdateWrapperBookDO wrapper new LambdaUpdateWrapper(); wrapper.set(BookDO::getState, state).setSql(version version 1).set(BookDO::getUpdatePin, userPin).set(BookDO::getUpdateTime, new Date()).eq(BookDO::getDeleteFlag, 0).in(BookDO::getId, idList); 分页查询 普通的分页查询功能通常需要两条sql一条查询总条数一条查询分页数据使用mybatis plus只需要配置一个分页插件myatis plus就可以在一条普通sql上添加分页功能。 具体事项 第一步配置分页插件 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor; }第二步调用selectPage方法 Test public void test8() {PageUser page new Page(1L, 10);User user new User();LambdaQueryWrapperUser wrapper new LambdaQueryWrapper();if (StringUtils.isNotBlank(user.getName())) {wrapper.like(User::getName, user.getName());}if (user.getAge() ! null) {wrapper.eq(User::getAge, user.getAge());}if (StringUtils.isNotBlank(user.getEmail())) {wrapper.eq(User::getEmail, user.getEmail());}wrapper.orderByDesc(User::getId);PageUser pageResult userMapper.selectPage(page, wrapper);ListUser records pageResult.getRecords();assert pageResult.getTotal() ! 0;assert !records.isEmpty(); }配置id的生成方式 mybatis plus默认使用雪花算法来生成id同时还支持其它配置方式。 配置id的使用方式 方式1配置某张表使用自增id Data TableName(user) public class User {TableId(type IdType.AUTO)private Long id;private String name;private Integer age;private String email; }方式2全局配置使用自增id 配置1 mybatis-plus.global-config.db-config.id-typeauto配置2 Configuration public class MybatisPlusConfig {// 添加全局配置使用自增idBeanpublic GlobalConfig globalConfig() {GlobalConfig globalConfig new GlobalConfig();GlobalConfig.DbConfig dbConfig new GlobalConfig.DbConfig();dbConfig.setIdType(IdType.AUTO);globalConfig.setDbConfig(dbConfig);return globalConfig;} }任选一种配置即可。 总结方式1的优先级大于方式2 源码解析 springboot整合mybatis-plus的相关源码 回顾一下springboot整合mybatis的方式 mybatis提供的启动器会向spring容器中注入两个beanSqlSessionFactory、SqlSessionTemplateSqlSessionFactory是创建SqlSession的工厂类SqlSession在mybatis中代表和数据库的一次会话是mybatis的核心抽象SqlSessionTemplate在SqlSession外面又包了一层它负责在调用SqlSession时进行某些额外操作例如清理缓存等。 用户还需要使用MapperScan注解指定mapper接口的所在包MapperScan注解会向容器中导入一个注册器这个注册器会扫描指定包下的所有接口把它们视为Mapper接口注入到spring容器中还会修改这些接口的bean信息把接口的类对象替换为MapperFactoryBean.class使用一个工厂类创建Mapper接口的代理类这个工厂类还会获取之前注入的SqlSessionTemplate通过它来执行sql。 这就是springboot整合mybatis的大致流程接下来看一下springboot是如何整合mybatis plus的。 mybatis plus提供的启动器 springboot整合mybatis-plus需要依赖mybatis-plus提供的启动器mybatis-plus-boot-starter 启动器的maven坐标 dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.2.0/version /dependency启动器中的依赖项依赖mybatis plus dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus/artifactIdversion3.5.1/versionscopecompile/scope /dependencymybatis plus的依赖项依赖 mybatis-plus-extension dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-extension/artifactIdversion3.5.1/versionscopecompile/scope /dependencymybatis-plus-extension的依赖项 dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-core/artifactIdversion3.5.1/versionscopecompile/scope /dependency dependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion2.0.6/versionscopecompile/scope /dependencymybatis-spring的依赖项 dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.6/versionscopeprovided/scope /dependency总结这里只介绍几个重要的依赖项可以看到用户只要依赖了mybatis plus的启动器它就会自动引用mybatis plus、mybatis spring、mybatis等启动mybatis spring负责mybatis和spring的整合mybatis提供的启动器同样会依赖到它。 启动器导入的配置类 打开启动器对应的jar包打开jar包中的spring.factoies文件文件中记录了mybatis plus提供的配置类这是springboot自动装配相关的部分。 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration\com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration从spring.factoies文件中可以看到MybatisPlusAutoConfiguration是spring容器启动时mybatis-plus注入的类 MybatisPlusAutoConfiguration的整体结构 Configuration(proxyBeanMethods false) // 自动装配的条件类路径下必须有SqlSessionFactory、SqlSessionFactoryBean这两个是mybatis中的类 // spring容器中必须要有数据库连接池 ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) ConditionalOnSingleCandidate(DataSource.class) // 解析springboot提供的配置项 EnableConfigurationProperties(MybatisPlusProperties.class) // 解析顺序在数据库连接池之后解析 AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class}) public class MybatisPlusAutoConfiguration implements InitializingBean {private static final Logger logger LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);// 构造方法spring容器会根据这个构造方法把它需要的实例传递给它public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,ObjectProviderInterceptor[] interceptorsProvider,ObjectProviderTypeHandler[] typeHandlersProvider,ObjectProviderLanguageDriver[] languageDriversProvider,ResourceLoader resourceLoader,ObjectProviderDatabaseIdProvider databaseIdProvider,ObjectProviderListConfigurationCustomizer configurationCustomizersProvider,ObjectProviderListMybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizerProvider,ApplicationContext applicationContext) {this.properties properties;this.interceptors interceptorsProvider.getIfAvailable();this.typeHandlers typeHandlersProvider.getIfAvailable();this.languageDrivers languageDriversProvider.getIfAvailable();this.resourceLoader resourceLoader;this.databaseIdProvider databaseIdProvider.getIfAvailable();this.configurationCustomizers configurationCustomizersProvider.getIfAvailable();this.mybatisPlusPropertiesCustomizers mybatisPlusPropertiesCustomizerProvider.getIfAvailable();this.applicationContext applicationContext;}// 当前类实现了InitializingBean接口这是实例化后的扩展点用于检测配置文件是否存在如果用户指定了的话Overridepublic void afterPropertiesSet() {if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) {mybatisPlusPropertiesCustomizers.forEach(i - i.customize(properties));}checkConfigFileExists();}private void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(),Cannot find config location: resource (please add config file or check your Mybatis configuration));}}// 注入beanSqlSessionFactory随后讲解SuppressWarnings(SpringJavaInjectionPointsAutowiringInspection)BeanConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 省略代码}// 注入beanSqlSessionTemplateBeanConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType this.properties.getExecutorType();if (executorType ! null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}// 一个内部配置类通常如果用户没有使用MapperScan注解就会引入这个配置类// 因为MapperScan注解会引入MapperScannerConfigurer扫描Mapper接口// 这个配置类会引入一个扫描Mapper接口的组件它会从spring容器的basePackage下// 扫描所有被Mapper接口标注的接口Configuration(proxyBeanMethods false)Import(AutoConfiguredMapperScannerRegistrar.class)ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {Overridepublic void afterPropertiesSet() {logger.debug(Not found configuration for registering mapper bean using MapperScan, MapperFactoryBean and MapperScannerConfigurer.);}}// 上面的内部配置类导入的注册器public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {private BeanFactory beanFactory;Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 省略代码}}总结启动器导入的配置类中向spring容器中注入了SqlSessionFactory、SqlSessionTemplate如果用户没有使用MapperScan注解的话还会额外注入一个扫描Mapper接口的注册器 。 配置类导入的bean 1、SqlSessionFactory SuppressWarnings(SpringJavaInjectionPointsAutowiringInspection) Bean ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBeanMybatisSqlSessionFactoryBean factory new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSource);// ... 省略代码mybatis相关的特性拦截器、类型处理器等// TODO 此处必为非 NULLGlobalConfig globalConfig this.properties.getGlobalConfig();// TODO 注入填充器this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);// TODO 注入主键生成器this.getBeansThen(IKeyGenerator.class, i - globalConfig.getDbConfig().setKeyGenerators(i));// TODO 注入sql注入器this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);// TODO 注入ID生成器this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);// TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBeanfactory.setGlobalConfig(globalConfig);return factory.getObject(); }上面这段源码中的中文注释都是自带的因为mybatis plus是国内公司开发的需要注意的是sql注入器、globalConfigsql注入器就是mybatis plus负责自动生成sql的组件globalConfig是全局配置。SqlSessionFactory是mybatis中的组件它持有配置类的实例mybatis plus提供了自己的配置类并且把它注入到了SqlSessionFactory中这就是mybatis plus可以在mybatis的基础上增加某些功能的原因。mybatis提供的配置类是mybatis的配置类的子类。 mybatis plus中提供的配置类 public class MybatisConfiguration extends Configuration {private static final Log logger LogFactory.getLog(MybatisConfiguration.class);/*** Mapper 注册这里就是自动注入相关的逻辑*/protected final MybatisMapperRegistry mybatisMapperRegistry new MybatisMapperRegistry(this);// 添加Mapper接口Overridepublic T void addMapper(ClassT type) {mybatisMapperRegistry.addMapper(type);}}顺着这个配置类就可以找到自动注入相关的逻辑。mybatis plus重写了mybatis解析Mapper接口的逻辑包括Mapper接口中可能存在的注解例如Select等。 这里直接看自动注入相关的逻辑不再看具体流程以注入selectList方法为例 public class SelectList extends AbstractMethod {public SelectList() {super(SqlMethod.SELECT_LIST.getMethod());}/*** param name 方法名* since 3.5.0*/public SelectList(String name) {super(name);}// 参数1是Mapper接口的类对象参数2是实体类的类对象参数3是根据实体类解析出的表信息包括表名、表字段、主键信息Overridepublic MappedStatement injectMappedStatement(Class? mapperClass, Class? modelClass, TableInfo tableInfo) {// 配置在枚举类中的常量SELECT_LIST(selectList, 查询满足条件所有数据, script%s SELECT %s FROM %s %s %s %s\n/script), SqlMethod sqlMethod SqlMethod.SELECT_LIST;// 这里就是拼接sql模板注意看上面sql语句中的占位符这里会把字段名、表名等信息拼接到sql语句中// 随后会根据LambdaQueryWrapper向这个sql模板中填充where条件String sql String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),sqlWhereEntityWrapper(true, tableInfo), sqlOrderBy(tableInfo), sqlComment());// 把sql注入到SqlSource中这是mybatis中的组件SqlSource sqlSource languageDriver.createSqlSource(configuration, sql, modelClass);return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);} }生成的sql案例这是以之前案例中的User类为例生成的selectList查询语句随后会根据LambdaQueryWrapper表达式向当前sql模板中注入查询条件 scriptif testew ! null and ew.sqlFirst ! null${ew.sqlFirst}/if SELECT choosewhen testew ! null and ew.sqlSelect ! null${ew.sqlSelect}/whenotherwiseid,name,age,email/otherwise/choose FROM user if testew ! nullwhereif testew.entity ! nullif testew.entity.id ! nullid#{ew.entity.id}/ifif testew.entity[name] ! null AND name#{ew.entity.name}/ifif testew.entity[age] ! null AND age#{ew.entity.age}/ifif testew.entity[email] ! null AND email#{ew.entity.email}/if/ifif testew.sqlSegment ! null and ew.sqlSegment ! and ew.nonEmptyOfWhereif testew.nonEmptyOfEntity and ew.nonEmptyOfNormal AND/if ${ew.sqlSegment} /if/whereif testew.sqlSegment ! null and ew.sqlSegment ! and ew.emptyOfWhere${ew.sqlSegment}/if/if if testew ! null and ew.sqlComment ! null${ew.sqlComment}/if /script其它默认的语句例如insert、delete也类似根据实体类信息解析出数据表信息然后根据预先定义好的sql模板这些sql’模板被定义在枚举类中然后向模板中注入表名、字段名等信息然后预留占位符随后根据LambdaQueryWrapper、LambdaUpdateWrapper等进一步拼接条件最终形成一条可执行的sql。 一共注入了哪些方法 // 默认的sql注入器这里暂不关心它在哪里被调用只关注它注入了哪些sql方法 public class DefaultSqlInjector extends AbstractSqlInjector {Overridepublic ListAbstractMethod getMethodList(Class? mapperClass, TableInfo tableInfo) {Stream.BuilderAbstractMethod builder Stream.AbstractMethodbuilder().add(new Insert()).add(new Delete()).add(new DeleteByMap()).add(new Update()).add(new SelectByMap()).add(new SelectCount()).add(new SelectMaps()).add(new SelectMapsPage()).add(new SelectObjs()).add(new SelectList()).add(new SelectPage());if (tableInfo.havePK()) { // 如果表中有主键builder.add(new DeleteById()).add(new DeleteBatchByIds()).add(new UpdateById()).add(new SelectById()).add(new SelectBatchByIds());} else {logger.warn(String.format(%s ,Not found TableId annotation, Cannot use Mybatis-Plus xxById Method.,tableInfo.getEntityType()));}return builder.build().collect(toList());} }总结这里介绍了注入SqlSessionFactory相关的逻辑mybatis plus重写了mybatis的配置类并且把它注入到了SqlSessionFactory中。在spring容器实例化创建Mapper接口的代理类的过程中最终会通过SqlSessionFactory调用mybatis plus提供的配置类创建Mapper接口的代理类在这个过程中会为BaseMapper接口中定义的方法创建相应的sql语句并且把它们加入到Mapper接口的代理类中。sql语句的相关信息被包装在MappedStatement实例中。这里只展示部分关键信息因为大部分是mybatis、springboot中的概念而且对mybatis plus中的流程也做了省略。 2、注入SqlSessionTemplate这里就比较简单了它是SqlSession的代理类和mybatis中的一样 Bean ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType this.properties.getExecutorType();if (executorType ! null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);} }LambdaQueryWrapper的执行过程 上一步中spring容器启动Mapper接口中的代理类创建成功并且为BaseMapper中定义的方法生成了sql语句生成的sql语句中已经包含了字段名、表名这两部分的内容剩下的内容例如where子句以占位符的形式存在接下来就看一下mybatis plus是如何把LambdaQueryWrapper渲染成where语句的。 LambdaQueryWrapper的继承体系 LambdaQueryWrapper // 泛型T需要传入实体类 public class LambdaQueryWrapperT extends AbstractLambdaWrapperT, LambdaQueryWrapperTimplements QueryLambdaQueryWrapperT, T, SFunctionT, ? {/*** 查询字段select子句后的列名如果用户有指定使用用户指定的*/private SharedString sqlSelect new SharedString(); }AbstractLambdaWrapperLambdaQueryWrapper的抽象父类它的泛型比较复杂泛型T代表实体类泛型Children代表子类所以Children需要继承AbstractLambdaWrapper public abstract class AbstractLambdaWrapperT, Children extends AbstractLambdaWrapperT, Childrenextends AbstractWrapperT, SFunctionT, ?, Children {// 缓存解析好的字段名这里会使用TableInfo中已经解析好的数据不会再解析一次private MapString, ColumnCache columnMap null;private boolean initColumnMap false; }AbstractWrapperAbstractLambdaWrapper的抽象父类这里定义了主要的逻辑 SuppressWarnings({unchecked}) public abstract class AbstractWrapperT, R, Children extends AbstractWrapperT, R, Children extends WrapperTimplements CompareChildren, R, NestedChildren, Children, JoinChildren, FuncChildren, R {/*** 占位符子类也会继承用于链式调用*/protected final Children typedThis (Children) this;// 存储where子句中的每个部分包括列名、运算符、值、逻辑运算符protected MergeSegments expression;/*** 数据库表映射实体类*/private T entity;/*** 实体类型(主要用于确定泛型以及取TableInfo缓存)*/private ClassT entityClass; }Wrapper public abstract class WrapperT implements ISqlSegment {/*** 实体对象子类实现** return 泛型 T*/public abstract T getEntity();/*** 获取 MergeSegments*/public abstract MergeSegments getExpression(); }ISqlSegment代表一个sql片段可以是列名也可以是运算符、值总之是sql语句中被空格分割的一部分 FunctionalInterface public interface ISqlSegment extends Serializable {/*** SQL 片段*/String getSqlSegment();}总结从这一些继承体系中可以看出LambdaQueryWrapper也可以理解为是一个sql片段 只是它是拼接好的sql片段 拼接sql片段 以like语句为例查看查询条件是如何被封装的 // 这是用户侧的调用案例like(StringUtils.isNotBlank(nameKeyword), User::getName, nameKeyword) // 看一下这个语句是如何被转换为sql语句的// like方法 Override public Children like(boolean condition, R column, Object val) {// 参数2like关键字// 参数5SqlLike.DEFAULT%值%它会自动在值的两侧拼接通配符// 其它参数都是用户传入的条件、列名、值return likeValue(condition, LIKE, column, val, SqlLike.DEFAULT); }// like方法的进一步调用这里实际上比较难以理解的地方在于maybeDo方法中第二个值是一个lambda表达式 protected Children likeValue(boolean condition, SqlKeyword keyword, R column, Object val, SqlLike sqlLike) {return maybeDo(condition, () - appendSqlSegments(columnToSqlSegment(column), keyword, () - formatParam(null, SqlUtils.concatLike(val, sqlLike)))); }// maybeDo方法如果条件为true执行上面第二个参数的lambda表达式 protected final Children maybeDo(boolean condition, DoSomething something) {if (condition) {something.doIt();}return typedThis; }// appendSqlSegments方法把sql片段添加到expression中。上面likeValue方法中appendSqlSegments // 方法的三个参数每一个都是ISqlSegement接口的实例columnToSqlSegment是从方法引用中解析出字段名 // formatParam是把值拼接为诸如 “#{字段名}” 的逻辑 protected void appendSqlSegments(ISqlSegment... sqlSegments) {expression.add(sqlSegments); }// formatParam方法封装占位符 #{} 的逻辑 protected final String formatParam(String mapping, Object param) {// 这里会拼接一个占位符然后再把它和值之间的映射存储起来占位符类似于 ew.paramNameValuePairs.MPGENVAL1// 最后的1是原子类自增// 占位符 WRAPPER_PARAM MPGENVAL// getParamAlias() ewWRAPPER_PARAM_MIDDLE .paramNameValuePairs DOT;// 注意paramAlias它的值是ew这是mybatis plus中默认的selectList方法被Param注解标注// Param注解指定参数名是ew而LambdaQueryWrapper的实例又是传递给selectList方法的实参// 所以“ew”会和LambdaQueryWrapper的实例绑定到一起。final String genParamName Constants.WRAPPER_PARAM paramNameSeq.incrementAndGet();final String paramStr getParamAlias() Constants.WRAPPER_PARAM_MIDDLE genParamName;// 占位符和值的映射paramNameValuePairs.put(genParamName, param);// 拼接 #{占位符}return SqlScriptUtils.safeParam(paramStr, mapping); }总结以like方法为例它生成like语句相关的sql片段最终依次存储到expression中sql片段包括 列名、like关键字、#{占位符}随后会按照顺序把它们拼接在一起。 这里只展示了外部的调用路径核心部分在随后的拼接过程中 关键字的拼接逻辑 关键字被组织在枚举类中并且枚举类实现了ISqlSegment接口这可以学习一下枚举类继承某个接口的实际应用 关键字 public enum SqlKeyword implements ISqlSegment {AND(AND),OR(OR),IN(IN),NOT(NOT),LIKE(LIKE),EQ(StringPool.EQUALS),NE(),GT(StringPool.RIGHT_CHEV),GE(),LT(StringPool.LEFT_CHEV),LE(),IS_NULL(IS NULL),IS_NOT_NULL(IS NOT NULL),GROUP_BY(GROUP BY),HAVING(HAVING),ORDER_BY(ORDER BY),EXISTS(EXISTS),BETWEEN(BETWEEN),ASC(ASC),DESC(DESC);private final String keyword;SqlKeyword(final String keyword) {this.keyword keyword;}Overridepublic String getSqlSegment() {return this.keyword;} }根据get方法的方法引用解析出字段名 在整体流程中这一个环节有必要单独拿出来介绍一下。 通过SFunction来接收方法引用例如 User::getName然后进一步解析。 SFunction 用于接收诸如 User::getName 等get方法mybatis plus会从这些lambda表达式中解析出字段名这是Java提供的能力 FunctionalInterface public interface SFunctionT, R extends FunctionT, R, Serializable { }// Function接口接收一个参数返回一个结果 public interface FunctionT, R {R apply(T t); }从方法引用中解析出字段名 // 参数方法引用例如 User::getName protected ColumnCache getColumnCache(SFunctionT, ? column) {// 这里使用了Java提供的能力把lambda表达式解析到 SerializedLambda 实例中LambdaMeta meta LambdaUtils.extract(column);// 根据get方法解析出字段名String fieldName PropertyNamer.methodToProperty(meta.getImplMethodName());Class? instantiatedClass meta.getInstantiatedClass();// 这里根据实体类的类对象获取之前解析好的缓存这是在spring容器启动时根据实体类解析TableInfo时做的tryInitCache(instantiatedClass);// 获取ColumnCache它包括实体类中的字段名、表中的列名return getColumnCache(fieldName, instantiatedClass); }// 根据get方法解析字段名的逻辑 public static String methodToProperty(String name) {if (name.startsWith(is)) {name name.substring(2);} else if (name.startsWith(get) || name.startsWith(set)) {name name.substring(3);} else {throw new ReflectionException(Error parsing property name name . Didnt start with is, get or set.);}if (name.length() 1 || (name.length() 1 !Character.isUpperCase(name.charAt(1)))) {name name.substring(0, 1).toLowerCase(Locale.ENGLISH) name.substring(1);}return name; }实际演示一下如何从User::getEmail中解析出getName的方法名称 // 第一步自定义函数式接口注意这个接口一定要支持序列化 FunctionalInterface public interface MyFuncT, R extends FunctionT, R, Serializable { }// 第二步解析方法名 Test public void test1() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {MyFuncUser, String func User::getEmail;Method method func.getClass().getDeclaredMethod(writeReplace);method.setAccessible(true);SerializedLambda lambdaMetadata (SerializedLambda) method.invoke(func);System.out.println(lambdaMetadata lambdaMetadata);String implMethodName lambdaMetadata.getImplMethodName();System.out.println(implMethodName implMethodName); // getEmail }原理讲解writeReplace 方法是 Java 序列化机制中的一个特殊方法。如果一个对象在序列化前定义了此方法那么序列化时会调用该方法并序列化它返回的对象而不是原始对象本身。SerializedLambda 是 JDK 为支持序列化 Lambda 表达式而引入的类。当一个可序列化的 Lambda 表达式例如实现了 Serializable 的接口被序列化时JDK 会通过内部的 writeReplace 方法将其转换成一个 SerializedLambda 对象这个对象包含了捕获该 Lambda 所需的所有信息如实现方法、目标类、捕获的参数等。 mybatis plus正式通过这种机制从方法引用中解析出方法名进而进一步解析出字段名 拼接过程 sql案例 public ListUser queryByCondition() {LambdaQueryWrapperUser lambdaQueryWrapper new LambdaQueryWrapper();lambdaQueryWrapper.notIn(User::getId, Lists.newArrayList(2000L, 3000L, 4000L)) .like(User::getName, a).gt(User::getAge, 20).and(wrapper - wrapper.isNotNull(User::getEmail).or().lt(User::getId, 1000L))// 先根据age正序排序然后根据id倒序排序.orderByAsc(User::getAge).orderByDesc(User::getId);return userMapper.selectList(lambdaQueryWrapper); }这是一条尽可能复杂的sql接下来了解一下mybatis plus是如何把它转换为sql语句的。有一点我一直没有注意到直到尝试了解mybatis plus的源码时才发现例如在notIn和like之间应该有一个and显然是mybatis plus内部做了这种拼接所以它是如何处理的 mybatis plus在把java代码转换为sql语句时使用了模板方法设计模式并且使用了继承、组合等多种方式来组织内部的组件我接下来会把所有代码都放在一起但是我会标清它的执行步骤希望可以讲清楚。 整体流程 MergeSegments整合sql片段之前提到会把sql片段的集合保存到expression中这里就是保存时内部的逻辑expression就是一个MergeSegments实例 // MergeSegments保存where子句后面包括普通查询条件、group by、order by等子句的sql片段 Getter SuppressWarnings(serial) public class MergeSegments implements ISqlSegment {// where 子句private final NormalSegmentList normal new NormalSegmentList();// group by子句private final GroupBySegmentList groupBy new GroupBySegmentList();// having 子句private final HavingSegmentList having new HavingSegmentList();// order by子句private final OrderBySegmentList orderBy new OrderBySegmentList();Getter(AccessLevel.NONE)private String sqlSegment StringPool.EMPTY;// 只要计算过一次这个值就是true下次直接走缓存Getter(AccessLevel.NONE)private boolean cacheSqlSegment true;// 第一步添加sql片段public void add(ISqlSegment... iSqlSegments) {// 把可变参转换成列表并且获取列表中的第一个元素ListISqlSegment list Arrays.asList(iSqlSegments);ISqlSegment firstSqlSegment list.get(0);// 根据第一个元素的值来决定sql片段是普通的where子句还是group by、order by等不同子句走不同分支if (MatchSegment.ORDER_BY.match(firstSqlSegment)) { orderBy.addAll(list); // order by} else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {groupBy.addAll(list); // group by} else if (MatchSegment.HAVING.match(firstSqlSegment)) {having.addAll(list); // having} else {normal.addAll(list); // where 子句}cacheSqlSegment false;}// 最后一步拼接最终结果Overridepublic String getSqlSegment() {if (cacheSqlSegment) {return sqlSegment;}cacheSqlSegment true;if (normal.isEmpty()) {if (!groupBy.isEmpty() || !orderBy.isEmpty()) {sqlSegment groupBy.getSqlSegment() having.getSqlSegment() orderBy.getSqlSegment();}} else {sqlSegment normal.getSqlSegment() groupBy.getSqlSegment() having.getSqlSegment() orderBy.getSqlSegment();}return sqlSegment;} }where子句的拼接 这里采用了模板方法设计模式父类设计流程子类重写某些关键步骤 // 抽象父类父类继承了ArrayList并且只可以存储ISqlSegment类型的元素同时实现了ISqlSegment接口 // StringPool是一个存储常量的接口。把常量写在接口中比写一个专门的常量类也许更合适吧毕竟接口中的 // 常量默认被public static final修饰。 public abstract class AbstractISegmentList extends ArrayListISqlSegment implements ISqlSegment, StringPool {/*** 当前这个类会存储所有的sql片段这个成员变量是存储集合中的最后一个值* 最后一个值之所以要单独存储是为了处理sql片段之间拼接and、or的逻辑*/ISqlSegment lastValue null;boolean flushLastValue false;// 缓存计算结果一个LambdaQueryWrapper实例只要计算一次结果就不会再变了。private String sqlSegment EMPTY;private boolean cacheSqlSegment true;// 第二步这里就是第一步中where子句的分支进入的方法添加普通的where条件到集合中Overridepublic boolean addAll(Collection? extends ISqlSegment c) {ListISqlSegment list new ArrayList(c);// 第三步子类重写transformList方法先处理sql片段例如// 普通的where子句会在sql片段的最后拼接andgroup by子句会把group by前缀去掉boolean goon transformList(list, list.get(0));if (goon) {// 第四步子类处理完集合后如果需要把sql片段拼接到最后结果中就返回true然后进入当前分支// 拼接sql片段刷新列表的最后一个值cacheSqlSegment false;if (flushLastValue) {this.flushLastValue(list);}return super.addAll(list);}return false;}// 子类重写这个方法这里会改变list内部的元素protected abstract boolean transformList(ListISqlSegment list, ISqlSegment firstSegment);// 把列表中最后一个元素保存到成员变量中private void flushLastValue(ListISqlSegment list) {lastValue list.get(list.size() - 1);}// void removeAndFlushLast() {remove(size() - 1);flushLastValue(this);}// 拼接最终结果Overridepublic String getSqlSegment() {if (cacheSqlSegment) {return sqlSegment;}cacheSqlSegment true;sqlSegment childrenSqlSegment();return sqlSegment;}protected abstract String childrenSqlSegment(); }负责拼接where子句的类 NormalSegmentList public class NormalSegmentList extends AbstractISegmentList {/*** 是否处理了的上个 not*/private boolean executeNot true;NormalSegmentList() {this.flushLastValue true;}// 第三步进入的方法这个方法中主要是负责处理and、or、not关键字在两个查询条件中拼接and、or或者移除最后一个and、orOverrideprotected boolean transformList(ListISqlSegment list, ISqlSegment firstSegment) {if (list.size() 1) {/* 只有 and() 以及 or() 以及 not() 会进入 */if (!MatchSegment.NOT.match(firstSegment)) {// 处理 and、orif (isEmpty()) {//sqlSegment是 and 或者 or 并且在第一位不继续执行return false;}boolean matchLastAnd MatchSegment.AND.match(lastValue);boolean matchLastOr MatchSegment.OR.match(lastValue);if (matchLastAnd || matchLastOr) {// 上次最后一个值是 and 或者 or那么不需要处理if (matchLastAnd MatchSegment.AND.match(firstSegment)) {return false;} else if (matchLastOr MatchSegment.OR.match(firstSegment)) {return false;} else {// 和上次的不一样刷新列表的最后一位removeAndFlushLast();}}} else {// 处理not在下一次调用时向sql片段中拼接not关键字executeNot false;return false;}} else {if (!executeNot) {// 下一次调用时会进入这里处理not包括 not in、not exists这里对于not的处理不容易理解// 举个例子用户进行如下调用 notIn(User::getId, Lists.newArrayList(2000L, 3000L, 4000L)),// notIn的内部会转换成 not(condition).in(condition, column, coll) 所以先处理not关键字// 它会把executeNot改成false然后返回false表示现在不需要拼接not在下一步处理in关键字时// 发现前面需要拼接not然后就会进入当前分支在in的前面拼接一个not。list.add(MatchSegment.EXISTS.match(firstSegment) ? 0 : 1, SqlKeyword.NOT);executeNot true;}// 如果当前集合不为空并且最后一个sql片段不是and在sql片段的最后拼接一个and这是最常见的情况if (!MatchSegment.AND_OR.match(lastValue) !isEmpty()) {add(SqlKeyword.AND);}if (MatchSegment.APPLY.match(firstSegment)) {list.remove(0);}}return true;}// 遍历sql片段获取其中的值这里就是执行之前传入的lambda表达式获取列名、关键字、字段占位符的字符串形式Overrideprotected String childrenSqlSegment() {if (MatchSegment.AND_OR.match(lastValue)) {removeAndFlushLast();}final String str this.stream().map(ISqlSegment::getSqlSegment).collect(Collectors.joining(SPACE));return (str.startsWith(LEFT_BRACKET) str.endsWith(RIGHT_BRACKET)) ? str : (LEFT_BRACKET str RIGHT_BRACKET);} }负责拼接order by子句的类 NormalSegmentList可以和拼接普通where条件的方法一起对照着看一下 public class OrderBySegmentList extends AbstractISegmentList {// 处理order by相关的sql片段时会移除order by关键字Overrideprotected boolean transformList(ListISqlSegment list, ISqlSegment firstSegment) {list.remove(0);if (!isEmpty()) { // 这里是判断最终结果集不为空拼接逗号然后再方法外会拼接参数中的listsuper.add(() - COMMA);}return true;}// 获取最终结果Overrideprotected String childrenSqlSegment() {if (isEmpty()) {return EMPTY;}// 拼接order by后的列名时同时会拼接order by关键字作为前缀return this.stream().map(ISqlSegment::getSqlSegment).collect(joining(SPACE, SPACE ORDER_BY.getSqlSegment() SPACE, EMPTY));} }这里把所有的sql片段拼接好之后随后在执行sql的时候会解析sql语句中的占位符例如 #{ew.sqlSegment}通过解析对象表达式然后调用对象中的方法获取这里拼接好的sql片段。 最终拼接结果这里做了部分美化随后会使用mybatis提供的能力把 “#{}” 之类的参数替换为值LambdaQueryWrapper中存储了这些参数和实际值的映射关系。 SELECT id,name,age,email FROM users WHERE (id NOT IN (#{ew.paramNameValuePairs.MPGENVAL1},#{ew.paramNameValuePairs.MPGENVAL2},#{ew.paramNameValuePairs.MPGENVAL3}) AND name LIKE #{ew.paramNameValuePairs.MPGENVAL4} AND age #{ew.paramNameValuePairs.MPGENVAL5} AND ( (email IS NOT NULL OR id #{ew.paramNameValuePairs.MPGENVAL6})) ) ORDER BY age ASC , id DESC 执行过程 用户调用selectList方法传入LambdaQueryWrapper然后执行sql上面已经看到了selectList方法生成的sql模板接下来看一下LambdaQueryWrapper是如何被渲染到where子句中的。 接下来的功能完成是依赖myatis中的能力mybatis plus在之前构建好了selectList方法对应的sql模板又在LambdaQueryWrapper中设置了查询条件和占位符接下来会根据sql模板来解析查询条件中的数据 这是mybtais中的代码参数parameterObject就包含了LambdaQueryWrapper Override public BoundSql getBoundSql(Object parameterObject) {DynamicContext context new DynamicContext(configuration, parameterObject);// 根据参数中的数据解析sql模板这里是动态sql的一部分根据值来决定使用哪些sql片段rootSqlNode.apply(context);SqlSourceBuilder sqlSourceParser new SqlSourceBuilder(configuration);Class? parameterType parameterObject null ? Object.class : parameterObject.getClass();// 生成数据库可执行的sqlSqlSource sqlSource sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());BoundSql boundSql sqlSource.getBoundSql(parameterObject);context.getBindings().forEach(boundSql::setAdditionalParameter);return boundSql; }接下来就是mybatis中的功能和mybatis plus无关了mybatis plus只是负责根据Java代码生成sql执行是依赖mybatis提供的能力 总结 作为一个java程序员日常开发就是写一些增删改查页面mybatis plus很适合这种场景因为大部分情况都是单表查询我们会把用户一次操作需要的数据尽量放到一张表中而且sql也不复杂mybatis plus减少了很多样板代码相当强大。一直很好奇它是怎么做到的在这里做个简要了解。 mybatis plus重写了mybatis的配置类、以及为Mapper接口创建代理类的逻辑因为重写了配置类在通过mybatis的SqlSessionFactory创建Mapper接口的代理类时才会进入mybatis plus的逻辑。在创建Mapper接口的代理类时会为BaseMapper接口中的方法创建sql语句这里使用了mybatis中sql标签技术sql中的某些部分支持参数替换例如selectList方法它的查询条件预留了占位符在随后使用LambdaQueryWrapper构建where、group by等条件时会使用构建好的结果替换占位符而且这是使用了mybatis的能力所以mybatis plus相当于mybatis是无侵入的它完全是在mybatis的基础上增强了某些能力。 使用LambdaQueryWrapper是如何构建查询条件的首先sql语句中的每一个节点无论是列表、关键字还是值都被抽象为一个ISqlSegment接口它负责返回这些节点的字符串形式然后AbstractISegmentList实现了ISqlSegment并且继承了ArrayList负责把sql片段组合到一个列表中而且它进一步做了细化分出了多个子类并且使用模板方法设计模式提取出公共逻辑例如普通的where子句使用NormalSegmentList处理and、or等关键字的拼接order by子句使用OrderBySegmentList处理order by关键字和列名之间的逗号最后MergeSegments外部组件只需要把sql片段传给它由它来负责整体处理拼接逻辑它持有NormalSegmentList、OrderBySegmentList等实例。所以在这里既有继承、也有组合但是组件之间的职责和架构很清晰。 这里只是简单了解了整体流程还有很多复杂的细节没有涉及有时间再深入了解。
http://www.zqtcl.cn/news/243148/

相关文章:

  • 杭州响应式网站案例建筑工程网站建站方案
  • 网站访客抓取国内网站搭建
  • 凡科网站做的好不好太原网页
  • 十堰商城网站建设国外效果图网站
  • 怎么登陆建设工程网站泉州网红
  • 哈尔滨队网站网页美工跨境电商是什么意思
  • 网站规划与建设课程推广型网站建设软件
  • 山东网站建设系统网站设计哪家更好
  • 网络推广有哪些网站网络推广公司联系昔年下拉
  • 长沙专业外贸建站公司百度提交网站入口网站
  • 西安网站搭建建设定制市场营销推广策略
  • 用户等待网站速度公司网站怎么做站外链接
  • 设计新闻发布网站模板wonder audio wordpress
  • 教育与培训网站建设wordpress侧栏文章
  • 四川做网站的公司哪家好信誉好的赣州网站建设
  • seo外包网站网站的备案流程图
  • 学网站建设好么免费网页制作有哪些
  • 宁波公司网站开发招聘最便宜的视频网站建设
  • 找人做网站大概多少钱永州企业网站建设
  • 免费备案网站空间网站怎么做组织图
  • 四川省和城乡建设厅网站怎么做网站淘宝转换工具
  • 网站单页支付宝支付怎么做的排名优化公司口碑哪家好
  • 淄博网站制作服务推广做网站服务器配置
  • ppt做的好的有哪些网站有哪些广州品牌型网站建设
  • 怎么学做一件完整衣服网站网站 相对路径
  • 十大wordpress主题江门seo排名优化
  • 石家庄网站搭建定制在百度上如何上传自己的网站
  • 南宁建设厅官方网站福州中小企业网站制作
  • 模板网站建设平台昆山专业网站建设公司哪家好
  • 百度指数的数值代表什么网站建设优化的作用