`

jpa

jpa 
阅读更多

序言

自工作以来,除了以前比较流量的hibernate,就是一直使用ORM 规范 JPA了.而这几天工作需要,研究了下JPA的标准查询,名为:JPA criteria查询.相比JPQL,其优势是类型安全,更加的面向对象.

使用标准查询,开发人员可在编译的时候就检查查询的正确与否.而以前也只是在Hibernate中听说有过.具体不详,没用过.

用的maven插件生成的.具体看这些把.
Hibernate

org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor
http://relation.to/Bloggers/HibernateStaticMetamodelGeneratorAnnotationProcessor
OpenJPA

org.apache.openjpa.persistence.meta.AnnotationProcessor6
http://openjpa.apache.org/builds/latest/docs/manual/manual.html#d0e11094
DataNucleus

org.datanucleus.jpa.JPACriteriaProcessor
http://www.datanucleus.org/products/accessplatform_2_1/jpa/jpql_criteria_metamodel.html

一.JPA元模型概念,及使用

在JPA中,标准查询是以元模型的概念为基础的.元模型是为具体持久化单元的受管实体定义的.这些实体可以是实体类,嵌入类或者映射的父类.提供受管实体元信息的类就是元模型类.

描述受管类的状态和他们之间的关系的静态元模型类可以

  • 1.从注解处理器产生
  • 2.从程序产生
  • 3.用EntityManager访问.

如下code,一个简单的实体类package com.demo.entities;下,实体类Employee ,假设该实体有诸如id,name和age的基本属性,还有与类Address的OneToMany关联:

@Entity
@Table
public class Employee{  
	private int id;   
	private String name;
	private int age;
	@OneToMany
	private List<Address> addresses;
	// Other code…
}

Employee类(com.demo.entities包中定义)的标准元模型类的名字将是使用 javax.persistence.StaticMetamodel注解的Employee_。元模型类的属性全部是static和public的。Employee的每一个属性都会使用在JPA2规范中描述的以下规则在相应的元模型类中映射:

  • 诸如id,name和age的非集合类型,会定义静态属性SingularAttribute<A, B> b,这里b是定义在类A中的类型为B的一个对象。
  • 对于Addess这样的集合类型,会定义静态属性ListAttribute<A, B> b,这里List对象b是定义在类A中类型B的对象。其它集合类型可以是SetAttribute, MapAttribute 或 CollectionAttribute 类型。

 以下是用注解处理器产生的元模型类package com.demo.entities;下:

import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcesso")
@StaticMetamodel(Employee.class)
public classEmployee_ {     
	public static volatile SingularAttribute<Employee, Integer> id;   
	public static volatile SingularAttribute<Employee, Integer> age;   
	public static volatile SingularAttribute<Employee, String> name;    
	public static volatile ListAttribute<Employee, Address> addresses;
}

就像它的名字表明的,注解处理器处理注解,帮助产生源代码注解处理在编译时就能激活。元模型类遵循JPA2.0规范中为定义标准元模型类而描述的规则创建。

使用元模型类最大的优势是凭借其实例化可以在编译时访问实体的持久属性.该特性使得criteria 查询更加类型安全.

元模型API与Java中的标准反射API密切相关。主要不同在于使用标准反射API编译器无法验证其正确性。例如:下面的代码会通过编译测试:

Class myClass = Class.forName("com.demo.Test");
Field myField = myClass.getField("myName");

编译器假定com.demo.Test中定义了属性myName,一旦该类并没有定义属性myName,编译器将抛出运行时异常。

元模型API会强制编译器检查适当的值是否分配给实体类的持久属性。例如:考虑Employee类的age属性,它是Integer变量。若该属性被赋值为String类型的值,编译器会抛出错误。该实现并不要求支持非标准特性。程序员编写的元模型类通常称为非标准元模型类。当EntityManagerFactory 创建时,持久化提供者会初始化元模型类的属性。

二.使用criteria 查询简单Demo

为了更好的理解criteria 查询,考虑拥有Employee实例集合的Dept实体,Employee和Dept的元模型类的代码如下:

//All Necessary Imports
@StaticMetamodel(Dept.class)
public classDept_ {    
	public static volatile SingularAttribute<Dept, Integer> id;   
	public static volatile ListAttribute<Dept, Employee> employeeCollection;    
	public static volatile SingularAttribute<Dept, String> name;
}
//All Necessary Imports
@StaticMetamodel(Employee.class)
public classEmployee_ {     
	public static volatile SingularAttribute<Employee, Integer> id;    
	public static volatile SingularAttribute<Employee, Integer> age;    
	public static volatile SingularAttribute<Employee, String> name;    
	public static volatile SingularAttribute<Employee, Dept> deptId;
}

下面的代码片段展示了一个criteria 查询,它用于获取所有年龄大于24岁的员工:

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
criteriaQuery.where(condition);
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();

对应的SQL: SELECT * FROM employee WHERE age > 24

三.构建CriteriaQuery 实例API说明

1.CriteriaBuilder 安全查询创建工厂,创建CriteriaQuery,创建查询具体具体条件Predicate 等

CriteriaBuilder是一个工厂对象,安全查询的开始.用于构建JPA安全查询.可以从EntityManager 或 EntityManagerFactory类中获得CriteriaBuilder. 
比如: CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();

2.CriteriaQuery 安全查询主语句

CriteriaQuery对象必须在实体类型或嵌入式类型上的Criteria 查询上起作用。
它通过调用 CriteriaBuilder, createQuery 或CriteriaBuilder.createTupleQuery 获得。
CriteriaBuilder就像CriteriaQuery 的工厂一样。
CriteriaBuilder工厂类是调用EntityManager.getCriteriaBuilder 或 EntityManagerFactory.getCriteriaBuilder而得。 
Employee实体的 CriteriaQuery 对象以下面的方式创建:

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);

3.Root 定义查询的From子句中能出现的类型

AbstractQuery是CriteriaQuery 接口的父类。它提供得到查询根的方法。 
Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似。 
Root实例也是类型化的,且定义了查询的FROM子句中能够出现的类型。 
查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得。 
Criteria查询,可以有多个查询根。 
Employee实体的查询根对象可以用以下的语法获得 : 

Root<Employee> employee = criteriaQuery.from(Employee.class);

 

4.Predicate 过滤条件

过滤条件应用到SQL语句的FROM子句中。 
在criteria 查询中,查询条件通过Predicate 或Expression 实例应用到CriteriaQuery 对象上。 
这些条件使用 CriteriaQuery .where 方法应用到CriteriaQuery 对象上。 
CriteriaBuilder 也是作为Predicate 实例的工厂,Predicate 对象通过调用CriteriaBuilder 的条件方法( equal,notEqual, gt, ge,lt, le,between,like等)创建。 
Predicate 实例也可以用Expression 实例的 isNull, isNotNull 和 in方法获得,复合的Predicate 语句可以使用CriteriaBuilder的and, or andnot 方法构建。 
下面的代码片段展示了Predicate 实例检查年龄大于24岁的员工实例:

Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
criteriaQuery.where(condition);

过Employee_元模型类age属性,称之为路径表达式。若age属性与String文本比较,编译器会抛出错误,这在JPQL中是不可能的。

5.Predicate[] 多个过滤条件

List<Predicate> predicatesList = new ArrayList<Predicate>();

predicatesList.add(.....Pridicate....)

criteriaQuery.where(predicatesList.toArray(new Predicate[predicatesList.size()]));

OR语句

 

predicatesList.add(criteriaBuilder.or(criteriaBuilder.equal(root.get(RepairOrder_.localRepairStatus), LocalRepairStatus.repairing),criteriaBuilder.equal(root.get(RepairOrder_.localRepairStatus), LocalRepairStatus.diagnos)));

 

忽略大小写(全大写)

 

predicatesList.add(criteriaBuilder.like(criteriaBuilder.upper(root.get(RepairShop_.shopName)), StringUtils.upperCase(StringUtils.trim(this.shopName)) + "%"));

通过如上两句添加多个.

 

6.TypedQuery执行查询与获取元模型实例

注意,你使用EntityManager创建查询时,可以在输入中指定一个CriteriaQuery对象,它返回一个TypedQuery,它是JPA 2.0引入javax.persistence.Query接口的一个扩展,TypedQuery接口知道它返回的类型。

所以使用中,先创建查询得到TypedQuery,然后通过typeQuery得到结果.

当EntityManager.createQuery(CriteriaQuery)方法调用时,一个可执行的查询实例会创建,该方法返回指定从 criteria 查询返回的实际类型的TypedQuery 对象。

TypedQuery 接口是javax.persistence.Queryinterface.的子类型。在该片段中, TypedQuery 中指定的类型信息是Employee,调用getResultList时,查询就会得到执行 
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();
元模型实例通过调用 EntityManager.getMetamodel 方法获得,EntityType<Employee>的元模型实例通过调用Metamodel.entity(Employee.class)而获得,其被传入 CriteriaQuery.from 获得查询根。

Metamodel metamodel = em.getMetamodel();EntityType<Employee> 
Employee_ = metamodel.entity(Employee.class);
Root<Employee> empRoot = criteriaQuery.from(Employee_);

也有可能调用Root.getModel方法获得元模型信息。类型 EntityType<Dept>的实例Dept_和name属性可以调用getSingularAttribute 方法获得,它与String文本进行比较:

CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root<Dept> dept = criteriaQuery.from(Dept.class);
EntityType<Dept> Dept_ = dept.getModel();
Predicate testCondition = criteriaBuilder.equal(dept.get(Dept_.getSingularAttribute("name", String.class)), "Ecomm");

7.Expression 用在查询语句的select,where和having子句中,该接口有 isNull, isNotNull 和 in方法

Expression对象用在查询语句的select,where和having子句中,该接口有 isNull, isNotNull 和 in方法,下面的代码片段展示了Expression.in的用法,employye的年龄检查在20或24的。 

CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.where(employee.get(Employee_.age).in(20, 24));
em.createQuery(criteriaQuery).getResultList();

对应的 SQL: SELECT * FROM employee WHERE age in (20, 24)

下面也是一个更贴切的例子:

 

//定义一个Expression
Expression<String> exp = root.get(Employee.id);
//
List<String> strList=new ArrayList<>();	
strList.add("20");
strList.add("24");		
predicatesList.add(exp.in(strList));

criteriaQuery.where(predicatesList.toArray(new Predicate[predicatesList.size()]));

8.复合谓词

Criteria Query也允许开发者编写复合谓词,通过该查询可以为多条件测试下面的查询检查两个条件。首先,name属性是否以M开头,其次,employee的age属性是否是25。逻辑操作符and执行获得结果记录。

criteriaQuery.where(
 criteriaBuilder.and(
  criteriaBuilder.like(employee.get(Employee_.name), "M%"), 
  criteriaBuilder.equal(employee.get(Employee_.age), 25)
));
em.createQuery(criteriaQuery).getResultList();

连接查询

在SQL中,连接跨多张表以获取查询结果,类似的实体连接通过调用 From.join 执行,连接帮助从一个实体导航到另一个实体以获得查询结果。 
Root的join方法返回一个 Join<Dept, Employee>类型(也可以是SetJoin,,ListJoin,MapJoin 或者 CollectionJoin类型)。

默认情况下,连接操作使用内连接,而外连接可以通过在join方法中指定JoinType参数为LEFT或RIGHT来实现。

CriteriaQuery<Dept> cqDept = criteriaBuilder.createQuery(Dept.class);
Root<Dept> deptRoot = cqDept.from(Dept.class);
Join<Dept, Employee> employeeJoin = deptRoot.join(Dept_.employeeCollection);
cqDept.where(criteriaBuilder.equal(employeeJoin.get(Employee_.deptId).get(Dept_.id), 1));
TypedQuery<Dept> resultDept = em.createQuery(cqDept);

抓取连接

当涉及到collection属性时,抓取连接对优化数据访问是非常有帮助的。这是通过预抓取关联对象和减少懒加载开销而达到的。 
使用 criteria 查询,fetch方法用于指定关联属性 
Fetch连接的语义与Join是一样的,因为Fetch操作不返回Path对象,所以它不能将来在查询中引用。 
在以下例子中,查询Dept对象时employeeCollection对象被加载,这不会有第二次查询数据库,因为有懒加载。

CriteriaQuery<Dept> d = cb.createQuery(Dept.class);
Root<Dept> deptRoot = d.from(Dept.class);
deptRoot.fetch("employeeCollection", JoinType.LEFT);
d.select(deptRoot);
List<Dept> dList = em.createQuery(d).getResultList();

对应SQL: SELECT * FROM dept d, employee e  WHERE d.id = e.deptId

路径表达式

Root实例,Join实例或者从另一个Path对象的get方法获得的对象使用get方法可以得到Path对象,当查询需要导航到实体的属性时,路径表达式是必要的。 
Get方法接收的参数是在实体元模型类中指定的属性。 
Path对象一般用于Criteria查询对象的select或where方法。例子如下:

CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<Dept> root = criteriaQuery.from(Dept.class);
criteriaQuery.select(root.get(Dept_.name));&nbsp;

参数化表达式

     在JPQL中,查询参数是在运行时通过使用命名参数语法(冒号加变量,如 :age)传入的。在Criteria查询中,查询参数是在运行时创建ParameterExpression对象并为在查询前调用TypeQuery,setParameter方法设置而传入的。下面代码片段展示了类型为Integer的ParameterExpression age,它被设置为24:

ParameterExpression<Integer> age = criteriaBuilder.parameter(Integer.class);
Predicate condition = criteriaBuilder.gt(testEmp.get(Employee_.age), age);
criteriaQuery.where(condition);
TypedQuery<Employee> testQuery = em.createQuery(criteriaQuery);
List<Employee> result = testQuery.setParameter(age, 24).getResultList();
Corresponding SQL: SELECT * FROM Employee WHERE age = 24;

排序结果

     Criteria查询的结果能调用CriteriaQuery.orderBy方法排序,该方法接收一个Order对象做为参数。通过调用  CriteriaBuilder.asc 或 CriteriaBuilder.Desc,Order对象能被创建。以下代码片段中,Employee实例是基于age的升序排列。  

CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
 Root<Employee> employee = criteriaQuery.from(Employee.class);
 criteriaQuery.orderBy(criteriaBuilder.asc(employee.get(Employee_.age)));
  em.createQuery(criteriaQuery).getResultList();

  对应  SQL: SELECT * FROM Employee ORDER BY age ASC

分组

CriteriaQuery 实例的groupBy 方法用于基于Expression的结果分组。查询通过设置额外表达式,以后调用having方法。下面代码片段中,查询按照Employee类的name属性分组,且结果以字母N开头: 
CriteriaQuery<Tuple> cq = criteriaBuilder.createQuery(Tuple.class); 

Root<Employee> employee = cq.from(Employee.class);
  cq.groupBy(employee.get(Employee_.name));
  cq.having(criteriaBuilder.like(employee.get(Employee_.name), "N%"));
cq.select(criteriaBuilder.tuple(employee.get(Employee_.name),criteriaBuilder.count(employee)));
  TypedQuery<Tuple> q = em.createQuery(cq);
  List<Tuple> result = q.getResultList();

对应  SQL:    SELECT name, COUNT(*) FROM employeeGROUP BY name HAVING name like 'N%'

查询投影

Criteria查询的结果与在Critiria查询创建中指定的一样。结果也能通过把查询根传入 CriteriaQuery.select中显式指定。Criteria查询也给开发者投影各种结果的能力。 

使用construct()

使用该方法,查询结果能由非实体类型组成。在下面的代码片段中,为EmployeeDetail类创建了一个Criteria查询对象,而EmployeeDetail类并不是实体类型。 

CriteriaQuery<EmployeeDetails> criteriaQuery = criteriaBuilder.createQuery(EmployeeDetails.class);
  Root<Employee> employee = criteriaQuery.from(Employee.class);
  criteriaQuery.select(criteriaBuilder.construct(EmployeeDetails.class, employee.get(Employee_.name), employee.get(Employee_.age)));
  em.createQuery(criteriaQuery).getResultList();
  Corresponding SQL: SELECT name, age FROM employee<span style="white-space: normal;">&nbsp;</span>

返回Object[]的查询

Criteria查询也能通过设置值给CriteriaBuilder.array方法返回Object[]的结果。下面的代码片段中,数组大小是2(由String和Integer组成)。 

CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
  Root<Employee> employee = criteriaQuery.from(Employee.class);
  criteriaQuery.select(criteriaBuilder.array(employee.get(Employee_.name), employee.get(Employee_.age)));
  em.createQuery(criteriaQuery).getResultList();

对应  SQL: SELECT name, age FROM employee

返回元组(Tuple)的查询

数据库中的一行数据或单个记录通常称为元组。通过调用CriteriaBuilder.createTupleQuery()方法,查询可以用于元组上。CriteriaQuery.multiselect方法传入参数,它必须在查询中返回。 

CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
   Root<Employee> employee = criteriaQuery.from(Employee.class);
   criteriaQuery.multiselect(employee.get(Employee_.name).alias("name"), employee.get(Employee_.age).alias("age"));
   em.createQuery(criteriaQuery).getResultList();

对应 SQL: SELECT name, age FROM employee 

结论

     Criteria查询是一种以更加面向对象的方式查询数据库的方法、在本文中,我讨论了JPA2中类型安全的Criteria查询,以及对于理解Criteria查询非常重要的元模型的概念。也讨论了Criteria查询中的各种API。

转的,觉得写的不错,整理到一个文章里了,地址:http://tanlan.iteye.com/blog/1101110

分享到:
评论

相关推荐

    jpa例子jpajpa

    **Java Persistence API (JPA)** 是Java平台上的一个标准,用于管理关系数据库中的对象-关系映射(ORM)。它提供了一种方式,让开发者可以用面向对象的编程模型来操作数据库,而无需直接编写SQL语句。JPA允许你在...

    Spring Data JPA中文文档[1.4.3]_springdatajpa_erlang_waitxpf_

    **Spring Data JPA** 是一个基于 **Java** 的开源框架,它是 **Spring Framework** 的一个模块,主要用于简化 **Java Persistence API (JPA)** 的使用。JPA 是 Java 平台上的一个标准,用于管理和持久化应用程序的...

    spring注解+spring data jpa文档+JPA文档.rar

    Spring框架的核心特性包括依赖注入(DI)和面向切面编程(AOP),并且它还提供了对数据库操作的支持,这主要通过Spring Data JPA和Java Persistence API(JPA)实现。 Spring注解是Spring框架中的一大特色,它极大...

    JPA分页查询与条件分页查询

    JPA(Java Persistence API)是Java平台上的一个标准,用于管理关系数据库中的数据。Spring Data JPA 是基于 JPA 的一个扩展,它简化了数据库操作,使得开发者无需编写大量SQL语句,就能实现数据的增删改查。在这个...

    JPA源文件/jpa学习

    **JPA(Java Persistence API)**是Java平台上的一个标准,用于管理关系数据库中的数据,它简化了在Java应用程序中存储、检索和管理对象的工作。JPA是Java EE和Java SE环境中的一种ORM(Object-Relational Mapping)...

    JPA (一)

    **Java 持久化 API(JPA)详解(一)** Java 持久化 API(Java Persistence API),简称 JPA,是 Java 平台上的一个标准,用于管理关系数据库中的数据。JPA 提供了一种面向对象的方式来操作数据库,使得开发人员...

    Spring Data JPA的优点和难点.pdf

    Spring Data JPA是Spring生态中的一个强大ORM框架,它极大地提高了Java开发者在处理数据库操作时的效率。Spring Data JPA的主要优点在于其高度的开发效率、成熟的语法结构以及与Spring框架的紧密集成。 1. **开发...

    java + spring boot +jpa 拦截器分库分表demo

    Spring Data JPA是Spring对JPA的扩展,提供了更丰富的功能,包括自动Repository生成、查询方法等。 在Spring Boot项目中集成JPA,首先需要在`pom.xml`或`build.gradle`中添加相应的依赖。接下来,创建实体类...

    Springboot中使用Druid+JPA

    在Spring Boot应用中,Druid和JPA是两种常见的数据库操作工具。Druid是一个功能强大的数据库连接池,而JPA(Java Persistence API)是Java平台上的一个标准,用于对象关系映射(ORM)。本篇文章将深入探讨如何在...

    Spring JPA 配置类包集合 方便大家使用

    Spring JPA,全称为Spring Data JPA,是Spring框架的一部分,专门用于简化Java持久层编程,尤其是与关系型数据库的交互。它建立在Java Persistence API(JPA)之上,提供了更高级别的抽象,使得开发者可以更加专注于...

    如何控制JPA的事务

    "JPA事务管理" JPA(Java Persistence API)是一种Java持久化规范,它提供了一个抽象层来访问关系数据库。JPA的事务管理是指对一系列操作的管理,包括创建、读取、更新和删除(CRUD)等操作。事务管理的目的是确保...

    黎活明老师讲解的JPA源码

    JPA用于整合现有的ORM技术,可以简化现有Java EE和Java SE应用的对象持久化的开发工作,实现ORM的统一。JPA作为一项对象持久化的标准,不但可以获得Java EE应用服务器的支持,还可以直接在Java SE中使用。JPA必将...

    使用SpringBoot-JPA进行自定义保存及批量保存功能

    使用SpringBoot-JPA进行自定义保存及批量保存功能 使用SpringBoot-JPA进行自定义保存及批量保存功能是指在Spring Boot应用程序中使用JPA(Java Persistence API)来实现自定义的保存和批量保存功能。JPA是一个Java ...

    Spring Data JPA从入门到精通

    《Spring Data JPA从入门到精通》是一套深入学习Spring Data JPA的资源,结合了两本书的内容,旨在帮助开发者全面掌握这一强大的数据访问技术。Spring Data JPA是Spring框架的一部分,它为Java Persistence API (JPA...

    spring boot jpa security

    Spring Boot JPA与Security是两个在Java开发领域中极为重要的技术框架,它们分别专注于数据访问和应用程序的安全管理。本文将深入探讨这两个框架的核心概念、如何整合使用以及在实际项目中的应用。 Spring Boot是由...

    jpa代码自动生成工具-springboot

    在Java开发领域,Spring Boot和JPA(Java Persistence API)是两个非常重要的框架。Spring Boot简化了Spring应用程序的创建和配置过程,而JPA作为Java的ORM(对象关系映射)标准,允许开发者以面向对象的方式操作...

    JPA中文解释,JPA的API

    Java Persistence API(JPA)是Java平台上的一个标准,用于管理关系数据库中的对象持久化。它简化了在Java应用程序中存储、检索和管理数据的过程,是Enterprise JavaBeans(EJB)的一部分,也是Spring框架中的一个...

    springboot + jpa 批量存储示例

    在本文中,我们将深入探讨如何使用Spring Boot与Java Persistence API (JPA) 实现批量存储操作。Spring Boot简化了设置和配置,而JPA作为Java的ORM(对象关系映射)框架,允许开发者以面向对象的方式操作数据库。...

Global site tag (gtag.js) - Google Analytics