`
g21121
  • 浏览: 694724 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

CommonDAO(公共/基础DAO)

 
阅读更多

        好久没有更新博客了,最近一段时间工作比较忙,所以请见谅,无论你是爱看呢还是爱看呢还是爱看呢,总之或许对你有些帮助。

        DAO(Data Access Object)是一个数据访问(顾名思义就是与数据库打交道)接口,DAO一般在业务逻辑与数据库资源之间,是我们访问数据库的桥梁。
        在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。

        今天就说一下在项目代码开发过程中经常会遇到的DAO方面问题:

        1.项目中模块众多,传统做法是每个模块编写一个独立的DAO接口及实现。这种情况下模块之间功能往往相似(传统增删改查),所以给程序员开发工作量上带来了不小的负担,而且维护起来也非常困难。

        2.采用1方式编写众多DAO接口,这些编写好的代码只能在本系统中使用,移植到其他系统中相当于重写,所以利用率非常低。

        3.一旦某个业务逻辑变更,带来的会是灾难性的代码修改问题,程序员需要逐个类的去修改每一个方法,难免会有遗漏和错误。

        4.每个程序员编写习惯及规范性的差异导致每个接口中定义的类名,方法名,参数名等等不一致,甚至千奇百怪。

        ..........

        等等。

 

        为了避免以上一些问题并且发挥出高级编程语言的高度复用性的优点,我们可以将具有相同特征、特性的DAO接口封装为一个公用或基础DAO,我们给这个DAO接口起名为CommonDAO。

        在CommonDAO中将共用方法(增删改查)提取,然后其他方法调用此DAO即可完成相同作用的工作,这样就节省了共用方法的这部分开发时间,而且维护起来也非常方便,下面就开始代码部分的讲解。

 

        1.定义CommonDAO接口,代码如下:

/**
 * 
 * @description 公共DAO
 * @author ming.li
 * @modify ming.li
 * @modifyDate 2014-1-6 下午12:29:21
 * @notes 未填写备注
 * @version 1.0.0
 * 
 */
public interface CommonDAO{
	/**
	 * 
	 * @description 添加
	 * @param o
	 * @return
	 * @returnType Integer
	 * @exception
	 * @since 1.0.0
	 */
	Integer add(Object o);
	/**
	 * 
	 * @description 删除
	 * @param id
	 * @param clazz 
	 * @returnType void
	 * @exception 
	 * @since  1.0.0
	 */
	boolean delete(Integer id,Class clazz);
	/**
	 * 
	 * @description 修改
	 * @param id 
	 * @param o 
	 * @returnType void
	 * @exception 
	 * @since  1.0.0
	 */
	boolean modify(Integer id,Object o);
	/**
	 * 
	 * @description  详情
	 * @param id
	 * @param clazz
	 * @return 
	 * @returnType Object
	 * @exception 
	 * @since  1.0.0
	 */
	Object detail(Integer id,Class clazz);
	/**
	 * 
	 * @description 根据QuerySet查询列表
	 * @param qs
	 * @param startIndex
	 * @param pageSize
	 * @return 
	 * @returnType ResultSet
	 * @exception 
	 * @since  1.0.0
	 */
	ResultSet queryByList(QuerySet qs, int startIndex, int pageSize);

}

         接口比较简单,含有增删改查几种基本方法。这里需要重点说下ResultSet queryByList(QuerySet qs, int startIndex, int pageSize);这个方法,我将查询参数与返回结果进行了封装,代码如下:

        2.QuerySet类:

/**
 * 
 * @description 查询参数集
 * @author ming.li
 * @modify ming.li
 * @modifyDate 2014-1-3 下午03:59:27
 * @notes 未填写备注
 * @version 1.0.0
 * 
 */
public class QuerySet implements java.io.Serializable {
	/**
	 * 模糊查询对象
	 */
	private Object fuzzyObject;

	/**
	 * 相等查询对象
	 */
	private Object exactObject;

	/**
	 * 自定义查询参数
	 */
	private String param;

	/**
	 * 排序
	 */
	private String order;

	public String getParam() {
		if (StringUtils.hasText(param)) {
			return param;
		}
		return "";
	}

	public String getOrder() {
		if (StringUtils.hasText(order))
			return order;
		else
			return "";
	}
	/** 省略部分get/set方法 **/
}

         3.ResultSet类:

import java.util.List;

/**
 * 
 * @description 查询结果集
 * @author ming.li
 * @modify ming.li
 * @modifyDate 2014-1-3 下午03:15:56
 * @notes 未填写备注
 * @version 1.0.0
 * 
 */
public class ResultSet implements java.io.Serializable{
	/**
	 * 查询结果列表
	 */
	private List resultList;
	/**
	 * 记录总数
	 */
	private int count;

	private ResultSet(List resultList, int count) {
		this.resultList = resultList;
		this.count = count;
	}
	/**
	 * 
	 * @description 获取结果集
	 * @param resultList
	 * @param count
	 * @return 
	 * @returnType ResultSet
	 * @exception 
	 * @since  1.0.0
	 */
	public static ResultSet getResultSet(List resultList, int count) {
		return new ResultSet(resultList, count);
	}
	/** 省略部分get/set方法 **/
}

         4.然后我们实现CommonDAO接口,实现类为CommonDAOImpl,代码如下:

import java.sql.SQLException;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.epro.uams.common.util.CollectionUtils;
import com.epro.uams.common.util.DAOUtils;
import com.epro.uams.common.vo.query.QuerySet;
import com.epro.uams.common.vo.query.ResultSet;
import com.epro.uams.dao.CommonDAO;

/**
 * 
 * @description 公共DAO
 * @author ming.li
 * @modify ming.li
 * @modifyDate 2014-1-6 下午12:32:56
 * @notes 未填写备注
 * @version 1.0.0
 * 
 */
public class CommonDAOImpl extends HibernateDaoSupport  implements CommonDAO{

	public Integer add(Object o) {
		return (Integer) this.getHibernateTemplate().save(o);
	}

	public boolean modify(Integer id, Object o) {
		Object target = getHibernateTemplate().get(o.getClass(), id);
		if (target != null) {
			DAOUtils.setParam(o, target);
			getHibernateTemplate().update(target);
			return true;
		}
		return false;
	}

	public boolean delete(Integer id, Class clazz) {
		Object target = getHibernateTemplate().get(clazz, id);
		if (target != null) {
			getHibernateTemplate().delete(target);
			return true;
		}
		return false;
	}
	
	public Object detail(Integer id, Class clazz) {
		return getHibernateTemplate().get(clazz, id);
	}
	
	public ResultSet queryByList(QuerySet qs, final int startIndex, final int pageSize) {
		final String hql = DAOUtils.getHql(qs);
		List result =this.getHibernateTemplate().executeFind(new HibernateCallback<Object>() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Query query = session.createQuery(hql);
				query.setFirstResult(startIndex);
				query.setMaxResults(pageSize);
				return query.list();
			}
		});
		final String countHql="select count(*) "+hql;
		int count = (int) this.getHibernateTemplate().execute(new HibernateCallback<Integer>() {
			public Integer doInHibernate(Session session) throws HibernateException, SQLException {
				Query query = session.createQuery(countHql);
				List result = query.list();
				if (!CollectionUtils.isEmpty(result))
					return Integer.parseInt(result.get(0).toString());
				else
					return 0;
			}
		});
		return ResultSet.getResultSet(result, count);
	}

}

        代码看不懂不要紧,接下来我慢慢讲解。

        1)定义一个公共接口CommonDAO,里面含有共用方法,这点大家应该都应该理解。

        2)CommonDAO里的参数及返回值都是Object类型,这样可以为所有对象使用。

        3)我将查询参数与查询返回结果进行了封装,在查询参数对象QuerySet中放入了一个模糊查询对象,一个精确查询对象以及其他参数属性和排序。

        下面解释下模糊查询对象与精确查询对象,其实他们的作用就是作为动态传参来用的,以往的查询方法例如:User queryUserInfo(Integer usrId,String name,int age.......等等参数),然后我们在queryUserInfo方法实现中会判断不同的参数是否存在,最后利用StringBuffer动态拼接一个HQL或者SQL进行查询。

        我想这样做过的朋友非常多吧,一旦参数很多或逻辑比较复杂程序员就崩溃了,采用了上面的两种对象后,我们只需要把想要查询的属性赋值后放置在QuerySet中即可,然后利用工具方法(后面讲解)进行动态拼装查询语句。

        示例:

        User(用户信息)类,含有以下属性:

private Integer userId;
private Integer type;
private String name;
private String tel;
private String mobile;
private String description;
private String email;
private Date createDate;

         如果我想根据name和emal查询该用户只需要:

User user=new User();
user.SetName("g21121");
user.SetEmail("g21121");
QuerySet qs=new QuerySet();
//根据对象的属性模糊查询
qs.setFuzzyObject(user);
//根据对象的属性精确查询
qs.setExactObject(user);

         最后调用查询方法queryByList(QuerySet qs, int startIndex, int pageSize)即可,返回结果会放置在ResultSet中。

         下面是DAOUtils的代码,其中基本为利用反射进行不同对象间赋值,根据对象属性动态组装查询语句等:

import java.lang.reflect.Field;

/**
 * 
 * @description DAO工具类
 * @author ming.li
 * @modify ming.li
 * @modifyDate 2014-1-7 下午02:56:14
 * @notes 未填写备注
 * @version 1.0.0
 * 
 */
public class DAOUtils {

	/**
	 * 
	 * @description 动态生成HQL
	 * @param o
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @returnType String
	 * @exception
	 * @since 1.0.0
	 */
	public static String getHql(QuerySet qs) {
		String tableName = "";
		if (qs.getExactObject() != null) {
			tableName = qs.getExactObject().getClass().getSimpleName();
		} else if (qs.getFuzzyObject() != null) {
			tableName = qs.getFuzzyObject().getClass().getSimpleName();
		} else {
			return "";
		}
		// 初始hql语句
		String hql = "from " + tableName + " ";
		// 精确查询条件
		String eql = getExactHql(qs.getExactObject());
		// 模糊查询条件
		String fql = getFuzzyHql(qs.getFuzzyObject());
		// 自定义参数
		String param = qs.getParam();
		// 排序
		String order = qs.getOrder();
		if (StringUtils.hasText(eql) && StringUtils.hasText(fql)) {
			// 两种条件均存在
			return hql + " where " + eql + " and (" + fql + ") " + getParam(param) + " " + order;
		} else if (StringUtils.hasText(eql) && !StringUtils.hasText(fql)) {
			// 只有精确查询条件
			return hql + " where " + eql + getParam(param) + " " + order;
		} else if (!StringUtils.hasText(eql) && StringUtils.hasText(fql)) {
			// 只有模糊查询条件
			return hql + " where " + "(" + fql + ") " + getParam(param) + " " + order;
		} else {
			return hql;
		}
	}

	/**
	 * 
	 * @description 获取额外参数
	 * @param param
	 * @return
	 * @returnType String
	 * @exception
	 * @since 1.0.0
	 */
	private static String getParam(String param) {
		if (StringUtils.hasText(param)) {
			int hasAnd = param.trim().toUpperCase().indexOf("AND");
			if (hasAnd == 0) {
				return " " + param;
			} else {
				return " and " + param;
			}
		}
		return "";
	}

	/**
	 * 
	 * @description 获取模糊查询条件
	 * @param fuzzyObject
	 * @return
	 * @returnType String
	 * @exception
	 * @since 1.0.0
	 */
	private static String getFuzzyHql(Object fuzzyObject) {
		if (fuzzyObject != null) {
			// 模糊查询对象
			Class fuzzyClass = fuzzyObject.getClass();

			// 获取模糊查询的属性
			try {
				StringBuffer sb = new StringBuffer("");
				// 是否含有查询条件
				// 获取属性列表
				Field[] fs = fuzzyClass.getDeclaredFields();
				// 循环添加属性查询条件
				for (Field f : fs) {
					f.setAccessible(true);
					Object value = f.get(fuzzyObject);
					String name = f.getName();
					if (value != null) {
						sb.append(" or " + name + " like '%" + value + "%'");
					}
				}
				// 判断是否含有查询条件
				String fql = sb.toString();
				if (StringUtils.hasText(fql) && fql.length() > 3)
					return fql.substring(3);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return "";
	}

	/**
	 * 
	 * @description 获取精确查询条件
	 * @param exactObject
	 * @return
	 * @returnType String
	 * @exception
	 * @since 1.0.0
	 */
	private static String getExactHql(Object exactObject) {
		if (exactObject != null) {
			// 精确查询对象
			Class exactClass = exactObject.getClass();

			// 取出需要准确查询的属性
			try {
				StringBuffer sb = new StringBuffer("");
				// 获取属性列表
				Field[] fs = exactClass.getDeclaredFields();
				// 循环添加属性查询条件
				for (Field f : fs) {
					f.setAccessible(true);
					Object value = f.get(exactObject);
					String name = f.getName();
					if (value != null) {
						if (f.getType().getName().equals("java.lang.String")) {
							sb.append(" and " + name + "='" + value + "'");
						} else {
							sb.append(" and " + name + "=" + value);
						}
					}
				}
				// 判断是否含有查询条件
				String eql = sb.toString();
				if (StringUtils.hasText(eql) && eql.length() > 3)
					return eql.substring(4);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return "";
	}

	/**
	 * 
	 * @description 设置要修改内容
	 * @param source
	 * @param target
	 * @return
	 * @returnType Object
	 * @exception
	 * @since 1.0.0
	 */
	public static Object setParam(Object source, Object target) {
		Class sClazz = source.getClass();
		Class tClazz = target.getClass();
		try {
			// 获取属性列表
			Field[] fs = sClazz.getDeclaredFields();
			for (Field f : fs) {
				f.setAccessible(true);
				Object value = f.get(source);
				String name = f.getName();
				if (value != null) {
					Field tf = tClazz.getDeclaredField(name);
					tf.setAccessible(true);
					tf.set(target, value);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return target;
	}

	/**
	 * 
	 * @description 查询Set VO转换成PO
	 * @param qs
	 * @param clazz
	 * @returnType void
	 * @exception
	 * @since 1.0.0
	 */
	public static void convertQuerySet(QuerySet qs, Class clazz) {
		if (qs != null) {
			try {
				// 精确查询对象
				if (qs.getExactObject() != null) {
					Object exact = clazz.newInstance();
					setParam(qs.getExactObject(), exact);
					qs.setExactObject(exact);
				}
				// 模糊查询对象
				if (qs.getFuzzyObject() != null) {
					Object fuzzy = clazz.newInstance();
					setParam(qs.getFuzzyObject(), fuzzy);
					qs.setFuzzyObject(fuzzy);
				}
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}

		}
	}

	/**
	 * 
	 * @description 去除动态拼接hql的最后几位
	 * @param hql
	 * @return
	 * @returnType String
	 * @exception
	 * @since 1.0.0
	 */
	public static String trimLastChar(String hql, int size) {
		return hql.substring(0, hql.length() - size);
	}
}

 

        以上就是所有核心代码,有不明白或错误的地方可以直接回帖。

5
1
分享到:
评论
9 楼 white_crucifix 2014-02-13  
引用
然后我们在queryUserInfo方法实现中会判断不同的参数是否存在,最后利用StringBuffer动态拼接一个HQL或者SQL进行查询。
        我想这样做过的朋友非常多吧



击中。。
8 楼 clxy 2014-02-13  
g21121 写道
clxy 写道
我的想法,供参考。

  • 使用优于继承。所以BaseDao不该定位成其他Dao的基类,我更倾向改成UtilDao或CommonDao,独立于其他Dao之外。需要时在业务成使用该类,没必要所有Dao继承它。
  • QuerySet也就罢了(我个人不喜欢对象化查询条件)。但ResultSet没必要加这么个类。用集合泛型就足够了。比如Collection<T>
  • 共通类里泛型还是可以该多多使用的

其他建议不错,但是我想一次返回结果和查询总数怎么办,不想调用两次方法


我自己的做法是将查询条件作为既是Input又是Output参数,封装成一个类。
比如:
public <T> Collection<T> queryByList(Condition condition)

Condition里面会有总数,分页等信息,查询总数后通过setter设回condition里去。

当然也不是什么好的方案,Input和Output混在一起,也许会让人有抵触...
7 楼 g21121 2014-02-13  
clxy 写道
我的想法,供参考。

  • 使用优于继承。所以BaseDao不该定位成其他Dao的基类,我更倾向改成UtilDao或CommonDao,独立于其他Dao之外。需要时在业务成使用该类,没必要所有Dao继承它。
  • QuerySet也就罢了(我个人不喜欢对象化查询条件)。但ResultSet没必要加这么个类。用集合泛型就足够了。比如Collection<T>
  • 共通类里泛型还是可以该多多使用的

其他建议不错,但是我想一次返回结果和查询总数怎么办,不想调用两次方法
6 楼 clxy 2014-02-13  
我的想法,供参考。

  • 使用优于继承。所以BaseDao不该定位成其他Dao的基类,我更倾向改成UtilDao或CommonDao,独立于其他Dao之外。需要时在业务成使用该类,没必要所有Dao继承它。
  • QuerySet也就罢了(我个人不喜欢对象化查询条件)。但ResultSet没必要加这么个类。用集合泛型就足够了。比如Collection<T>
  • 共通类里泛型还是可以该多多使用的
5 楼 g21121 2014-02-13  
string2020 写道
void delete(Integer id,Class clazz); 
void modify(Integer id,Object o); 

这两个应该返回Integer吧,否则,如何知道删除、修改的记录数?

删除方法本身就是void,当然你也可以添加业务逻辑返回值
4 楼 string2020 2014-02-13  
void delete(Integer id,Class clazz); 
void modify(Integer id,Object o); 

这两个应该返回Integer吧,否则,如何知道删除、修改的记录数?
3 楼 LinApex 2014-02-12  
out了,过时了
2 楼 dieslrae 2014-02-12  
表的主键不是int怎么办,有多主键的情况怎么办,BaseDao这个接口局限性太大了
1 楼 mail1239 2014-02-12  
spring mvc 呢

相关推荐

    IDEA逆向生成POJO/DAO/MAPPER 《Generate POJOs.groovy》

    没有统一格式化,所以在此基础上进行了扩展能够简单的生成pojo/dao/mapper. 使用时选择目录后会在改目录下生成对应的pojo/dao/mapper 文件,非常直观。dao和mapper 抛弃了mybatis插件生成的一个POJO一个mapper 很...

    操作系统学习资料

    操作系统是计算机科学的基础组成部分,它管理着计算机的硬件资源,为用户提供友好的接口,并协调各个程序的执行。这个压缩包中的文件显然是一份操作系统的学习资料,涵盖了操作系统的主要概念和原理。接下来,我们将...

    代码生成平台controller/service/dao/mybatis/model

    代码生成平台controller/service/dao/mybatis/model

    基于Java全栈开发的Nutz框架:Mvc/Ioc/Aop/Dao/Json一体化设计源码

    该项目为全栈Java开发,采用Nutz框架实现Mvc/Ioc/Aop/Dao/Json一体化设计,源码包含2307个文件,涵盖1742个Java源文件、208个man文档、143个png图片、47个jpg图片、22个js脚本、21个psd设计文件、16个xml文件、13个...

    java术语(PO/POJO/VO/BO/DAO/DTO)

    本文将详细解析"PO/POJO/VO/BO/DAO/DTO"这六个概念,并探讨它们在实际项目开发中的作用和应用场景。 1. PO(Persistent Object,持久化对象) PO是指与数据库表结构一一对应的Java对象,它通常包含了数据库表中的...

    登录代码(jsp/mvc/dao)

    综上,这个压缩包可能包含了三个不同层次的登录实现:JSP页面直接处理请求和显示结果,DAO负责数据库交互,而MVC模式中可能有一个Servlet作为控制器协调整个流程。开发者可以对比学习这三种方式的优缺点,理解它们在...

    Dao Jet数据库引擎

    在描述中提到的问题,“无法对DAO/Jet db引擎进行初始化”,通常是由于某些原因导致DAO或Jet引擎的组件损坏、缺失或版本不兼容。这可能是由于系统更新、病毒攻击、不正确的卸载或其他软件冲突造成的。解决这个问题的...

    基于jsp/servlet的DAO设计模工下的留言管理系统

    【基于jsp/servlet的DAO设计模工下的留言管理系统】是一个使用Java Web技术开发的应用程序,主要涉及的技术包括JavaServer Pages(JSP)、Servlet以及Data Access Object(DAO)设计模式。这个系统旨在实现对用户...

    android初学者DEMO-SQLite/dao

    2. **创建表**: 数据库中的表可能是以员工信息为基础的,如`Employee`表,字段可能包括`id`(主键)、`name`、`age`、`position`等。创建表的SQL语句可能如下: ```sql CREATE TABLE Employee ( id INTEGER ...

    dao 1.0 source code

    DAO(Data Access Object)模式是软件开发中常用的设计模式之一,主要用于数据库操作的抽象和封装。DAO 1.0 源代码的提供允许我们深入理解这一模式在实际项目中的应用。下面将详细介绍DAO模式的核心概念、作用以及...

    DAO基础学习资料

    DAO,全称Data Access Object(数据访问对象),是软件设计模式中的一种,主要用于数据库操作的封装,使得业务逻辑代码与数据库交互代码分离,提高代码的可重用性和可测试性。在本学习资料中,我们将重点探讨DAO在...

    mybatis自动生成bean/dao/mapper工具以及命令

    在实际开发中,为了提高效率,我们常常需要借助一些工具来自动生成Bean、DAO(Data Access Object)和Mapper接口,避免手动编写重复的代码。本话题将详细介绍如何使用MyBatis的自动生成工具,并提供相关的jar包和...

    Spring Boot Mybatis 自动生成Entity,controller、serviceImpl ,Dao,方便开发,无需手写

    当我们需要快速构建项目时,自动生成代码的功能显得尤为重要,因为它能够帮助我们节省大量手动编写基础架构代码的时间。本项目“Spring Boot Mybatis 自动生成Entity,controller、serviceImpl,Dao”正是这样一个...

    模板设计模式_构建公共通用的Dao

    在"模板设计模式_构建公共通用的Dao"的例子中,`MyJDBC2`可能是实现了通用Dao功能的具体类。这个类可能会包含以下关键部分: - **数据库连接管理**:通过连接池获取和释放数据库连接,这部分代码可以封装在一个模板...

    公共DAO层接口与实现

    公共DAO层接口与实现 1、dao层接口 2、Hibernate实现 3、Spring JDBC的面向对象实现方式

    策略设计模式_构建公共通用的Dao

    标题中的“策略设计模式_构建公共通用的Dao”指出,我们即将探讨的是如何利用策略设计模式来构建一个可复用的、通用的数据访问对象(DAO)层。在软件开发中,DAO层通常负责与数据库进行交互,执行增删改查等操作。...

    一些基础DAO类

    在这个名为"一些基础DAO类"的压缩包中,我们可以推测作者小山分享了一些他在实际开发中使用的DAO实现。 在Java或其他面向对象的语言中,DAO类通常包含以下关键元素: 1. **接口定义**:DAO接口定义了对数据库进行...

    Dao3.5数据库引擎

    DAO(Data Access Objects)是微软在早期开发的一种数据库访问技术,它是Microsoft Jet数据库引擎的一部分,主要用于与Access数据库的交互。DAO3.5是DAO的一个特定版本,它在Windows 95和Windows 98时代非常流行,但...

    dao jet数据库引擎

    当你的计算机上缺失这个组件时,依赖DAO接口来访问Jet数据库的应用程序可能会出现问题,例如描述中提到的"无法对DAO/Jet db引擎进行初始化"的错误。这个问题通常意味着系统缺少必要的DLL文件或者注册表项没有正确...

Global site tag (gtag.js) - Google Analytics