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

中山网站制作系统没有备案号的网站

中山网站制作系统,没有备案号的网站,泗阳做网站的,滨州建设局网站目录 MyBatis介绍 传统JDBC和Mybatis相比的弊病 传统JDBC的问题如下 mybatis对传统的JDBC的解决方案 Mybaits整体体系图 使用大致过程 MyBatis 源码编译 源码解析 配置文件解析 读取配置文件 返回SqlSessionFactory 配置文件内容 解析的核心方法 解析出来的对象 …目录 MyBatis介绍 传统JDBC和Mybatis相比的弊病 传统JDBC的问题如下 mybatis对传统的JDBC的解决方案 Mybaits整体体系图 使用大致过程 MyBatis 源码编译 源码解析 配置文件解析 读取配置文件 返回SqlSessionFactory 配置文件内容 解析的核心方法 解析出来的对象 mapper.xml 流程图 简单总结 执行SQL流程 简单总结 获取Mapper的流程 Mapper方法的执行流程 简单总结 重点     重要类 调试主要关注点 MyBatis介绍 MyBatis是一个持久层的ORM框架使用简单学习成本较低。可以执行自己手写的SQL语句比较灵活。但是MyBatis的自动化程度不高移植性也不高有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置所以称只为半自动ORM框架 传统JDBC和Mybatis相比的弊病 传统JDBC的问题如下 数据库连接创建释放频繁造成西戎资源的浪费从而影响系统性能使用数据库连接池可以解决问题。代码臃肿sql语句在代码中硬编码造成代码的不已维护实际应用中sql的变化可能较大sql代码和java代码没有分离开来维护不方便。使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定同样是修改不方便对结果集中解析存在硬编码问题sql的变化导致解析代码的变化系统维护不方便。 mybatis对传统的JDBC的解决方案 解决在SqlMapConfig.xml中配置数据连接池使用连接池管理数据库链接。解决将Sql语句配置在XXXXmapper.xml文件中与java代码分离。解决Mybatis自动将java对象映射至sql语句通过statement中的parameterType定义输入参数的类型。解决Mybatis自动将sql执行结果映射至java对象通过statement中的resultType定义输出结果的类型。 Mybaits整体体系图 我们把Mybatis的功能架构分为三层 API接口层提供外部使用的接口API开发人员通过这些本地API来操纵数据库接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。数据处理层负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。举出支撑层负责最基础的功能支撑包括连接管理、事务管理、配置加载和缓存处理这些都是共用的东西将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。 使用大致过程 从配置文件通常是XML文件得到SessionFactory;从SessionFactory得到SqlSession通过SqlSession进行CRUD和事务的操作执行完相关操作之后关闭Session。 MyBatis 源码编译 https://www.cnblogs.com/mokingone/p/9108999.html 源码解析 配置文件解析 读取配置文件 String resource mybatis-config.xml; //将XML配置文件构建为Configuration配置类 reader Resources.getResourceAsReader(resource); // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory SqlSessionFactory sqlMapper new SqlSessionFactoryBuilder().build(reader); 返回SqlSessionFactory 通过上面代码发现创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中进去一探究竟就是读进来解析 //整个过程就是将配置文件解析成Configration对象然后创建SqlSessionFactory的过程 //Configuration是SqlSessionFactory的一个内部属性 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException(Error building SqlSession., e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);} 下面我们看下解析配置文件过程中的一些细节。 配置文件内容 先给出一个配置文件的列子 ?xml version1.0 encodingUTF-8 ? !DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd configuration!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高config.properties配置文件的优先级次之properties标签中的配置优先级最低 --properties resourceorg/mybatis/example/config.propertiesproperty nameusername valuedev_user/property namepassword valueF2Fa3!33TYyg//properties!--一些重要的全局配置--settingssetting namecacheEnabled valuetrue/!--setting namelazyLoadingEnabled valuetrue/--!--setting namemultipleResultSetsEnabled valuetrue/--!--setting nameuseColumnLabel valuetrue/--!--setting nameuseGeneratedKeys valuefalse/--!--setting nameautoMappingBehavior valuePARTIAL/--!--setting nameautoMappingUnknownColumnBehavior valueWARNING/--!--setting namedefaultExecutorType valueSIMPLE/--!--setting namedefaultStatementTimeout value25/--!--setting namedefaultFetchSize value100/--!--setting namesafeRowBoundsEnabled valuefalse/--!--setting namemapUnderscoreToCamelCase valuefalse/--!--setting namelocalCacheScope valueSTATEMENT/--!--setting namejdbcTypeForNull valueOTHER/--!--setting namelazyLoadTriggerMethods valueequals,clone,hashCode,toString/--!--setting namelogImpl valueSTDOUT_LOGGING /--/settingstypeAliases/typeAliasespluginsplugin interceptorcom.github.pagehelper.PageInterceptor!--默认值为 false当该参数设置为 true 时如果 pageSize0 或者 RowBounds.limit 0 就会查询出全部的结果--!--如果某些查询数据量非常大不应该允许查出所有数据--property namepageSizeZero valuetrue//plugin/pluginsenvironments defaultdevelopmentenvironment iddevelopmenttransactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.jdbc.Driver/property nameurl valuejdbc:mysql://10.59.97.10:3308/windty/property nameusername valuewindty_opr/property namepassword valuewindty!234//dataSource/environment/environmentsdatabaseIdProvider typeDB_VENDORproperty nameMySQL valuemysql /property nameOracle valueoracle //databaseIdProvidermappers!--这边可以使用package和resource两种方式加载mapper--!--package name包名/--!--mapper resource./mappers/SysUserMapper.xml/--mapper resource./mappers/CbondissuerMapper.xml//mappers/configuration 解析的核心方法 下面是解析配置文件的核心方法 private void parseConfiguration(XNode root) {try {//issue #117 read properties first//解析properties标签并set到Configration对象中//在properties配置属性后在Mybatis的配置文件中就可以使用${key}的形式使用了。propertiesElement(root.evalNode(properties));//解析setting标签的配置Properties settings settingsAsProperties(root.evalNode(settings));//添加vfs的自定义实现这个功能不怎么用loadCustomVfs(settings);//配置类的别名配置后就可以用别名来替代全限定名//mybatis默认设置了很多别名参考附录部分typeAliasesElement(root.evalNode(typeAliases));//解析拦截器和拦截器的属性set到Configration的interceptorChain中//MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下MyBatis 允许使用插件来拦截的方法调用包括//Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)//ParameterHandler (getParameterObject, setParameters)//ResultSetHandler (handleResultSets, handleOutputParameters)//StatementHandler (prepare, parameterize, batch, update, query)pluginElement(root.evalNode(plugins));//Mybatis创建对象是会使用objectFactory来创建对象一般情况下不会自己配置这个objectFactory使用系统默认的objectFactory就好了objectFactoryElement(root.evalNode(objectFactory));objectWrapperFactoryElement(root.evalNode(objectWrapperFactory));reflectorFactoryElement(root.evalNode(reflectorFactory));//设置在setting标签中配置的配置settingsElement(settings);//解析环境信息包括事物管理器和数据源SqlSessionFactoryBuilder在解析时需要指定环境id如果不指定的话会选择默认的环境//最后将这些信息set到Configration的Environment属性里面environmentsElement(root.evalNode(environments));//databaseIdProviderElement(root.evalNode(databaseIdProvider));//无论是 MyBatis 在预处理语句PreparedStatement中设置一个参数时还是从结果集中取出一个值时 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析typeHandler。typeHandlerElement(root.evalNode(typeHandlers));//解析MappermapperElement(root.evalNode(mappers));} catch (Exception e) {throw new BuilderException(Error parsing SQL Mapper Configuration. Cause: e, e);} } 解析出来的对象 configuration对象 mapper.xml 解析 流程图 上面解析流程结束后会生成一个Configration对象包含所有配置信息然后会创建一个SqlSessionFactory对象这个对象包含了Configration对象。 简单总结 对于MyBatis启动的流程获取SqlSession的过程这边简单总结下 SqlSessionFactoryBuilder解析配置文件包括属性配置、别名配置、拦截器配置、环境数据源和事务管理器、Mapper配置等解析完这些配置后会生成一个Configration对象这个对象中包含了MyBatis需要的所有配置然后会用这个Configration对象创建一个SqlSessionFactory对象这个对象中包含了Configration对象 执行SQL流程 之前提到拿到sqlSession之后就能进行各种CRUD操作了所以我们就从sqlSession.getMapper这个方法开始分析看下整个Sql的执行流程是怎么样的 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx null;try {final Environment environment configuration.getEnvironment();final TransactionFactory transactionFactory getTransactionFactoryFromEnvironment(environment);tx transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//获取执行器这边获得的执行器已经代理拦截器的功能见下面代码final Executor executor configuration.newExecutor(tx, execType);//根据获取的执行器创建SqlSessionreturn new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException(Error opening session. Cause: e, e);} finally {ErrorContext.instance().reset();}} Copy //interceptorChain生成代理类具体参见Plugin这个类的方法 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType executorType null ? defaultExecutorType : executorType;executorType executorType null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH executorType) {executor new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE executorType) {executor new ReuseExecutor(this, transaction);} else {executor new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor new CachingExecutor(executor);}executor (Executor) interceptorChain.pluginAll(executor);return executor;} Executor分成两大类一类是CacheExecutor另一类是普通Executor。 普通Executor又分为三种基本的Executor执行器SimpleExecutor、ReuseExecutor、BatchExecutor。 SimpleExecutor每执行一次update或select就开启一个Statement对象用完立刻关闭Statement对象。ReuseExecutor执行update或select以sql作为key查找Statement对象存在就使用不存在就创建用完后不关闭Statement对象而是放置于Map内供下一次使用。简言之就是重复使用Statement对象。BatchExecutor执行update没有selectJDBC批处理不支持select将所有sql都添加到批处理中addBatch()等待统一执行executeBatch()它缓存了多个Statement对象每个Statement对象都是addBatch()完毕后等待逐一执行executeBatch()批处理。与JDBC批处理相同。 作用范围Executor的这些特点都严格限制在SqlSession生命周期范围内。 CacheExecutor其实是封装了普通的Executor和普通的区别是在查询前先会查询缓存中是否存在结果如果存在就使用缓存中的结果如果不存在还是使用普通的Executor进行查询再将查询出来的结果存入缓存。 到此为止我们已经获得了SqlSession拿到SqlSession就可以执行各种CRUD方法了。 简单总结 拿到SqlSessionFactory对象后会调用SqlSessionFactory的openSesison方法这个方法会创建一个Sql执行器Executor这个Sql执行器会代理你配置的拦截器方法。获得上面的Sql执行器后会创建一个SqlSession默认使用DefaultSqlSession,这个SqlSession中也包含了Configration对象所以通过SqlSession也能拿到全局配置获得SqlSession对象后就能执行各种CRUD方法了 一些重要类总结 SqlSessionFactorySqlSessionFactoryBuilderSqlSession默认使用DefaultSqlSessionExecutor接口Plugin、InterceptorChain的pluginAll方法 获取Mapper的流程 进入sqlSession.getMapper方法会发现调的是Configration对象的getMapper方法 public T T getMapper(ClassT type, SqlSession sqlSession) {//mapperRegistry实质上是一个Map里面注册了启动过程中解析的各种Mapper.xml//mapperRegistry的key是接口的Class类型//mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy动态代理类return mapperRegistry.getMapper(type, sqlSession); } 进入getMapper方法 public T T getMapper(ClassT type, SqlSession sqlSession) {final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type);//如果配置文件中没有配置相关Mapper,直接抛异常if (mapperProxyFactory null) {throw new BindingException(Type type is not known to the MapperRegistry.);}try {//关键方法return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException(Error getting mapper instance. Cause: e, e);}} 进入MapperProxyFactory的newInstance方法 public class MapperProxyFactoryT {private final ClassT mapperInterface;private final MapMethod, MapperMethod methodCache new ConcurrentHashMapMethod, MapperMethod();public MapperProxyFactory(ClassT mapperInterface) {this.mapperInterface mapperInterface;}public ClassT getMapperInterface() {return mapperInterface;}public MapMethod, MapperMethod getMethodCache() {return methodCache;}//生成Mapper接口的动态代理类MapperProxyMapperProxy实现了InvocationHandler 接口SuppressWarnings(unchecked)protected T newInstance(MapperProxyT mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxyT mapperProxy new MapperProxyT(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}} 获取Mapper的流程总结如下 Mapper方法的执行流程 下面是动态代理类MapperProxy调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法注意由于Mybatis中的Mapper接口没有实现类所以MapperProxy这个代理对象中没有委托类也就是说MapperProxy干了代理类和委托类的事情。好了下面重点看下invoke方法。 //MapperProxy代理类 public class MapperProxyT implements InvocationHandler, Serializable {private static final long serialVersionUID -6424540398559729838L;private final SqlSession sqlSession;private final ClassT mapperInterface;private final MapMethod, MapperMethod methodCache;public MapperProxy(SqlSession sqlSession, ClassT mapperInterface, MapMethod, MapperMethod methodCache) {this.sqlSession sqlSession;this.mapperInterface mapperInterface;this.methodCache methodCache;}Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//获取MapperMethod并调用MapperMethodfinal MapperMethod mapperMethod cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);} MapperProxy的invoke方法非常简单主要干的工作就是创建MapperMethod对象或者是从缓存中获取MapperMethod对象。获取到这个对象后执行execute方法。 所以这边需要进入MapperMethod的execute方法这个方法判断你当前执行的方式是增删改查哪一种并通过SqlSession执行相应的操作。这边以sqlSession.selectOne这种方式进行分析~ public Object execute(SqlSession sqlSession, Object[] args) {Object result;//判断是CRUD那种方法switch (command.getType()) {case INSERT: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result null;} else if (method.returnsMany()) {result executeForMany(sqlSession, args);} else if (method.returnsMap()) {result executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result executeForCursor(sqlSession, args);} else {Object param method.convertArgsToSqlCommandParam(args);result sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result sqlSession.flushStatements();break;default:throw new BindingException(Unknown execution method for: command.getName());}if (result null method.getReturnType().isPrimitive() !method.returnsVoid()) {throw new BindingException(Mapper method command.getName() attempted to return null from a method with a primitive return type ( method.getReturnType() ).);}return result;} sqlSession.selectOne方法会会调到DefaultSqlSession的selectList方法。这个方法获取了获取了MappedStatement对象并最终调用了Executor的query方法。  public E ListE selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException(Error querying database. Cause: e, e);} finally {ErrorContext.instance().reset();}} 然后通过一层一层的调用这边省略了缓存操作的环节会在后面的文章中介绍最终会来到doQuery方法 这儿咱们就随便找个Excutor看看doQuery方法的实现吧我这儿选择了SimpleExecutor: public E ListE doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt null;try {Configuration configuration ms.getConfiguration();//内部封装了ParameterHandler和ResultSetHandlerStatementHandler handler configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt prepareStatement(handler, ms.getStatementLog());//StatementHandler封装了Statement, 让 StatementHandler 去处理return handler.Equery(stmt, resultHandler);} finally {closeStatement(stmt);}} 接下来咱们看看StatementHandler 的一个实现类 PreparedStatementHandler这也是我们最常用的封装的是PreparedStatement, 看看它使怎么去处理的 public E ListE query(Statement statement, ResultHandler resultHandler) throws SQLException {//到此原形毕露 PreparedStatement, 这个大家都已经滚瓜烂熟了吧PreparedStatement ps (PreparedStatement) statement;ps.execute();//结果交给了ResultSetHandler 去处理,处理完之后返回给客户端return resultSetHandler.E handleResultSets(ps);} 到此整个调用流程结束。 简单总结 这边结合获取SqlSession的流程做下简单的总结 SqlSessionFactoryBuilder解析配置文件包括属性配置、别名配置、拦截器配置、环境数据源和事务管理器、Mapper配置等解析完这些配置后会生成一个Configration对象这个对象中包含了MyBatis需要的所有配置然后会用这个Configration对象创建一个SqlSessionFactory对象这个对象中包含了Configration对象拿到SqlSessionFactory对象后会调用SqlSessionFactory的openSesison方法这个方法会创建一个Sql执行器Executor组件中包含了Transaction对象这个Sql执行器会代理你配置的拦截器方法。获得上面的Sql执行器后会创建一个SqlSession默认使用DefaultSqlSession,这个SqlSession中也包含了Configration对象和上面创建的Executor对象所以通过SqlSession也能拿到全局配置获得SqlSession对象后就能执行各种CRUD方法了。 以上是获得SqlSession的流程下面总结下本博客中介绍的Sql的执行流程 调用SqlSession的getMapper方法获得Mapper接口的动态代理对象MapperProxy调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法动态代理机制MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象然后调用这个对象的execute方法sqlSession会作为execute方法的入参往下层层调下来会进入Executor组件如果配置插件会对Executor进行动态代理的query方法这个方法中会创建一个StatementHandler对象这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值使用ParameterHandler来给sql设置参数。 Executor组件有两个直接实现类分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件Transction组件中又分装了Datasource组件。 调用StatementHandler的增删改查方法获得结果ResultSetHandler对结果进行封装转换请求结束。 Executor、StatementHandler 、ParameterHandler、ResultSetHandlerMybatis的插件会对上面的四个组件进行动态代理。 重点     重要类 MapperRegistry本质上是一个Map其中的key是Mapper接口的全限定名value的MapperProxyFactoryMapperProxyFactory这个类是MapperRegistry中存的value值在通过sqlSession获取Mapper时其实先获取到的是这个工厂然后通过这个工厂创建Mapper的动态代理类MapperProxy实现了InvocationHandler接口Mapper的动态代理接口方法的调用都会到达这个类的invoke方法MapperMethod判断你当前执行的方式是增删改查哪一种并通过SqlSession执行相应的操作SqlSession作为MyBatis工作的主要顶层API表示和数据库交互的会话完成必要数据库增删改查功能ExecutorMyBatis执行器是MyBatis 调度的核心负责SQL语句的生成和查询缓存的维护 StatementHandler:封装了JDBC Statement操作负责对JDBC statement 的操作如设置参数、将Statement结果集转换成List集合。 ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数 ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合 TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换 MappedStatement:MappedStatement维护了一条节点的封装 SqlSource:负责根据用户传递的parameterObject动态地生成SQL语句将信息封装到BoundSql对象中并返回 BoundSql:表示动态生成的SQL语句以及相应的参数信息 Configuration:MyBatis所有的配置信息都维持在Configuration对象之中 调试主要关注点 MapperProxy.invoke方法MyBatis的所有Mapper对象都是通过动态代理生成的任何方法的调用都会调到invoke方法这个方法的主要功能就是创建MapperMethod对象并放进缓存。所以调试时我们可以在这个位置打个断点看下是否成功拿到了MapperMethod对象并执行了execute方法。MapperMethod.execute方法这个方法会判断你当前执行的方式是增删改查哪一种并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。DefaultSqlSession.selectList方法这个方法获取了获取了MappedStatement对象并最终调用了Executor的query方法
http://www.yingshimen.cn/news/49701/

相关文章:

  • 高新西区网站建设自助网站开发
  • 有品质的网站推广公司自己做网站服务器的备案方法
  • 载带 东莞网站建设wordpress好难用
  • 网站被人做跳转公司网页设计
  • 东莞松山湖网站建设长沙房价
  • 做内贸的网站wordpress模板工作室
  • 名字找备案网站阿里云做哪里查网站
  • 云南省网站建设收费调查报告论文北京云网站建设
  • 佛山网站代运营准度科技有限公司网站建设使用的什么软件
  • 网站风格定位天津专业网站制作流程优势
  • 西宁工程建设招聘信息网站网络推广项目代理
  • 怎么看网站文章的收录西安网站建设ruiqinet
  • 济南的企业网站建设网络营销品牌有哪些
  • php企业网站源码广东平台网站建设找哪家
  • 临沂做wish网站wordpress加背景音乐
  • 优服优科网站建设公司wordpress数据库表分析
  • 网站建设玖金手指谷哥三十站内优化
  • asp.net 网站安全个人博客登录首页
  • 家装公司网站微信如何建网站
  • 中联汇科 网站建设全景图网页制作工具
  • 中国电商网站排名photoshop+做网站logo
  • 网站建设 视频室内设计招聘网站有哪些
  • 怎样管理一个俄语网站台州建设工程信息网站
  • 广州建网站模板国外网站在国内备案
  • 215做网站专业的外贸网站建设公司价格
  • 网站内怎样做关键词有效果wordpress的restapi
  • 风险网站怎么解决方案小公司自己怎样做网站
  • 网站维护流程wordpress批量上传图片
  • 微信游戏网站源码怎么做怎么做网站自动采集数据库
  • 网站定做微信学校网站模板