`

hibernate缓存机制以及n+1次查询问题

阅读更多

本文借鉴至:http://www.cnblogs.com/xiaoluo501395377/p/3377604.html

一:N+1次查询问题。

首先,什么是N+1次查询,我的理解是在使用session.createQuery("HQL语句").iterator()查询时第一次查询会去查询数据库中所有符合条件的记录的id,然后根据id逐一查询出每条记录的现象。

下面来看看代码:

/**
     * N+1次查询问题
     */
    @Test
    public void testOnePlusMore() {
        Session session = null;
        try{
            session  = HibernateUtils.getSession();
            session.beginTransaction();
            Query query = session.createQuery("from User");
            Iterator<User> userIterator = query.iterate();
            User user;
            while(userIterator.hasNext()) {
                user = userIterator.next();
                System.out.println(user);
            }

        }catch (Exception e) {
            session.getTransaction().rollback();
        }
    }

 测试结果:

 

Hibernate: 
    select
        user0_.id as col_0_0_ 
    from
        t_user user0_
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=1, name='aaa', age=12}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=2, name='历史', age=22}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=3, name='333', age=33}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=4, name='阿什顿', age=22}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=5, name='奥斯达', age=22}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=6, name='33', age=44}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=7, name='333', age=434}

 为了做好对比,来看看list()方法查询所有的记录产生的现象是怎样的呢?

/**
     * 通过Query对象获取所有的记录
     */
    @Test
    public void testList() {
        Session session = null;
        try{
            session  = HibernateUtils.getSession();
            session.beginTransaction();
            Query query = session.createQuery("from User");
            List<User> userList = query.list();
            for(User user:userList) {
                System.out.println(user);
            }

        }catch (Exception e) {
            session.getTransaction().rollback();
        }
    }

 测试结果:

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.age as age2_0_,
        user0_.name as name3_0_ 
    from
        t_user user0_
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}

 从两次测试结果可以看出,iterator方法产生了大量的sql语句,并且查询n条记录的时候会产生n+1次查询,而这多的一次是去查询所有记录的ID而产生的。

其实到这里,我也比较疑惑既然list()方法能够一次性将所有的数据加载出来,那还用iterator()方法来干什么呢?感觉完全是多此一举的行为。但是,看到此文关联文章上面描述了一种场景:就是在一个session中两次查询出很多对象的时候,如果单单使用list()方法会造成两次查询的语句一样,这样就有一点浪费了。这里如果我们第一次使用list()方法,第二次就使用iterator()方法的话,此时我们也会发两条sql语句,但是第二条语句只会将查询出对象的id,所以相对应取出所有的对象而已,显然这样可以节省内存,而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。嗯,感觉这种说法确实是实际存在的,虽然没有在实际中碰到过,这里先记下吧。

二:hibernate一级缓存

前面的描述中又提到一个新东西:hibernate一级缓存。那么这个一级缓存又是个什么样的情况呢?

先用测试代码来看看这个现象吧:

 

/**
     * 测试一级缓存
     */
    @Test
    public void testFirstCache() {
        Session session = null;
        try{
            session = HibernateUtils.getSession();
            session.beginTransaction();
            //第一次查询
            User user = (User)session.get(User.class,1);
            System.out.println(user);
            System.out.println("----------------------------");
            //第二次查询
            User user1 = (User)session.get(User.class,1);
            System.out.println(user1);
            session.getTransaction().commit();
            session.close();
        }catch (Exception e) {
            session.getTransaction().rollback();
            session.close();
        }
    }

 

 这里在一个session中进行两次查询(查询的对象要相同,或者第二次查询的对象已经在第一次查询中出现),这种情况下hibernate给我们的查询方式为:

 

 

Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=1, name='aaa', age=12}
----------------------------
User{id=1, name='aaa', age=12}

 可以看到这里至进行了一个查询,第二次查询的时候根本没有发送sql语句出去,可以说这就是我们的hibernate一级缓存产生的现象:hiberante会把查询的结果缓存在session中,session没有关闭之前要是有这个对象的缓存,那我们可以直接从这个session中取出这个对象而不用发送sql语句再次到数据库中去查询注意:一级缓存的前提是session级别,也就是session范围内的缓存,在session没有关闭之前缓存的是有效的,但是session关闭之后缓存在那个session中的内容也不会存在了。

三:hibernate二级缓存(sessionFactory级别)

那么如果这样一种场景:我需要在session关闭之后还是能够取到之前查询出来的记录,这个该怎么办呢?这个就需要一个叫做二级缓存的东西的帮助了。

那什么是二级缓存呢?二级缓存是将查询的记录缓存在sessionFactory中。

先来说说具体使用吧:

1.hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包是EHcache。这个我们在下载好的hibernate的lib->optional->ehcache下可以找到(我这里使用的hibernate4.1.7版本),然后将里面的几个jar包导入即可。

2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:

<!-- 开启二级缓存 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类-->
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
        <!-- 二级缓存配置文件的位置 -->
        <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>

 

我这里使用的是hibernate4.3.8版本,如果是使用hibernate3的版本的话,那么二级缓存的提供类则要配置成这个:

 

<!--这个类在4.0版本以后已经不建议被使用了-->
<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>

 

 3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下

 

 

<ehcache>
    <!--指定二级缓存存放在磁盘上的位置-->
    <diskStore path="user.dir"/>
    <!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置-->
    <defaultCache
            maxElementsInMemory="10000"           //在内存中存放的最大对象数
            eternal="false"         //是否永久保存缓存,设置成false
    timeToIdleSeconds="120"    
    timeToLiveSeconds="120"    
    overflowToDisk="true"     //如果对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true
            />
    <!--
        1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;
        2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;
        3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
        4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
        5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。

      -->
    <!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名-->
    <cache name="cn.bdx.po.User"
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            />
</ehcache>

4.开启我们的二级缓存

①如果使用xml配置,我们需要在 User.hbm.xml 中加上一下配置:

<hibernate-mapping package="cn.bdx.po">
    <class name="User" table="t_user">
        <!-- 二级缓存一般设置为只读的 -->
        <cache usage="read-only"/>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name" type="string"></property>
        <property name="age" column="age" type="int"></property>
            </class>
</hibernate-mapping>

 二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。

②如果使用annotation配置,我们需要在User这个类上加上这样一个注解:

package cn.bdx.po;

import org.hibernate.annotations.*;
import org.hibernate.annotations.Cache;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;

/**
 * Created by Administrator on 2016/5/23.
 */
@Entity
@Table(name="t_user")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)//表示开启二级缓存,并设置为只读
public class User{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 到这里基本上所有的配置算是完毕了,接下来是测试的代码:

 

 

 

 /**
     * 测试二级缓存
     */
    @Test
    public void testEhcache() {
        Session session = null;
        try
        {
            session = HibernateUtils.getSession();

            User user = (User) session.get(User.class, 1);
            System.out.println(user.getName() + "-----------");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            session.close();
        }
        try
        {
            /**
             * 即使当session关闭以后,因为配置了二级缓存,而二级缓存是sessionFactory级别的,所以会从缓存中取出该数据
             * 只会发出一条sql语句
             */
            session = HibernateUtils.getSession();
            User user = (User) session.get(User.class, 1);
            System.out.println(user.getName() + "-----------");
            /**
             * 因为设置了二级缓存为read-only,所以不能对其进行修改
             *//*
            session.beginTransaction();
            user.setName("aaa");
            session.getTransaction().commit();*/
        }
        catch (Exception e)
        {
            e.printStackTrace();
            session.getTransaction().rollback();
        }
        finally
        {
            session.close();
        }
    }

测试结果:

Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
aaa-----------
aaa-----------
从结果可以看出即使session关闭,我们仍然不会发送sql语句查询数据库取获取记录,这就是二级缓存的存在,在配置了二级缓存之后,查询的记录也会缓存到sessionFactory中,这样无论session是否关闭我们只要sessionFactory存在我们还是可以从缓存中获取记录。注意一点:二级缓存是缓存的对象,对于获取单个或多个字段这样的内容是不予以缓存的。
四:查询缓存(sessionFactory级别)
首先二级缓存不能缓存hql语句,所以这里用到查询缓存的作用就是缓存hql语句。
使用查询缓存,只需要在hibernate.cfg.xml中加入一条配置即可:
 <!-- 开启查询缓存 -->
        <property name="hibernate.cache.use_query_cache">true</property>
 然后需要缓存的对象上面添加@Cacheable注解,表示要使用查询缓存;最后就是在代码中添加query.setCaheable(true),如下:
List<User> userList = session.createQuery("from User where id=?")
                    .setCacheable(true)//开启查询缓存
                    .setParameter(0,1)
                    .list();
 接下来看看测试代码:
/**
     * 测试查询缓存
     */
    @Test
    public void testQueryCache() {
        Session session = null;
        try{
            session = HibernateUtils.getSession();
            session.beginTransaction();
            List<User> userList = session.createQuery("from User")
                                            .setCacheable(true)//开启查询缓存
                                            .list();
            session.getTransaction().commit();
            for(User user:userList) {
                System.out.println(user);
            }
        }catch (Exception e) {
            session.getTransaction().rollback();
        }finally {
            session.close();
        }
        try{
            session = HibernateUtils.getSession();
            session.beginTransaction();
            List<User> userList = session.createQuery("from User")
                    .setCacheable(true)//开启查询缓存
                    .list();
            session.getTransaction().commit();
            for(User user:userList) {
                System.out.println(user);
            }
        }catch (Exception e) {
            session.getTransaction().rollback();
        }finally {
            session.close();
        }
    }
 测试结果:
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.age as age2_0_,
        user0_.name as name3_0_ 
    from
        t_user user0_
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.name as name3_0_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}
说明一下:这里我关闭了二级缓存进行的测试,从测试结果可以看到,在两次都使用list()进行查询的时候,第二次会出现n+1次查询问题,为什么呢?

因为查询缓存缓存的也仅仅是对象的id,所以第一条 sql 也是将对象的id都查询出来,但是当我们后面如果要得到每个对象的信息的时候,此时又会发sql语句去查询,所以,如果要使用查询缓存,我们一定也要开启我们的二级缓存,这样就不会出现 N+1 问题了。

将前面关闭的二级缓存重新打开:

@Entity
@Table(name="t_user")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)//表示开启二级缓存,并设置为只读
@Cacheable
public class User{

 这里重新打开二级缓存,然后运行上面的测试代码,

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.age as age2_0_,
        user0_.name as name3_0_ 
    from
        t_user user0_
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}

 可以看到这次只发送了一次sql语句,并且也没有存在n+1次查询问题了。所以:在使用查询缓存的时候建议将二级缓存打开,这样可以避免n+1次查询问题。 

使用查询缓存需要记住两点:1.查询缓存也是sessionFactory级别的缓存

                                    2.②只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效

分享到:
评论

相关推荐

    Hibernate缓存机制

    - 当使用`iterate`遍历查询结果时,可能会引发N+1次查询的问题。 - “N+1”中的“1”是指第一次查询,而“N”则是指后续每次查询数据库获取单条记录的次数。 #### 二、缓存使用注意事项 **2.1 效率问题** - ...

    hibernate缓存的问题

    N+1次查询问题是指在首次使用iterate()方法执行条件查询时,会产生额外的n次查询,但在后续相同查询中,性能会得到显著提升。对于大数据量的业务,应当考虑配置合适的缓存策略,避免内存资源过度消耗。 使用...

    Hibernate缓存,性能优化

    - **减少N+1查询问题**:通过懒加载、提前加载或批处理查询策略,避免因多个关联对象的加载而产生的大量额外查询。 - **精简SQL语句**:优化HQL或Native SQL语句,减少不必要的JOIN操作,利用索引和统计信息,提高...

    内容管理系统(hibernate3+struts2+spring2)130224.zip

    此外,确保正确配置数据库连接和实体映射,避免出现N+1查询或性能瓶颈。安全方面,Struts2的安全漏洞需要关注,及时更新到安全版本。 综上所述,内容管理系统(hibernate3+struts2+spring2)130224.zip是一个典型的...

    hibernate入门学习笔记+源码

    5. **查询优化**: 避免N+1查询问题,合理使用JOIN,减少数据库交互次数。 **六、源码分析** `hibernate_test`、`hibernate_test3`、`hibernate_test2`这些文件可能包含了示例项目的源代码,包括配置文件、实体类、...

    jsp源码内容管理系统(hibernate3+struts2+spring2)

    - **缓存机制**:为了提高性能,Hibernate 提供了一级缓存和二级缓存的支持,可以有效地减少数据库访问次数。 - **事务管理**:支持细粒度的事务控制,确保数据的一致性和完整性。 ##### 2. Struts 2 Struts 2 是一...

    Hibernate缓存详解[文].pdf

    Hibernate缓存机制是Java开发中使用Hibernate框架进行数据库操作时非常关键的一个部分,它能够显著提升应用程序的性能。本文将详细解析Hibernate的一级缓存和二级缓存。 **一级缓存** 一级缓存是SessionFactory创建...

    Java+Persistence+with+Hibernate

    - 合理设计实体结构,避免N+1查询问题。 - 使用批处理操作提高性能。 - 注意事务粒度,平衡性能与数据一致性。 - 慎重使用JOIN,避免大数据量的笛卡尔积。 通过学习《Java+Persistence+with+Hibernate》,开发者...

    struts2+hibernate3.3图书管理系统

    5. **二级缓存**:Hibernate支持二级缓存,可以提高性能,尤其是在读多写少的应用中。 在"struts2+hibernate3.3图书管理系统"项目中,这两个框架协同工作,实现图书的增删改查功能。Struts2负责接收用户的HTTP请求...

    spring+hibernate+struts页面

    同时,还需要注意优化性能,如合理使用缓存,避免N+1查询等,以提升系统整体性能。总之,Spring、Hibernate和Struts的结合使用,为Java Web开发提供了强大的工具集,使得开发者能够更加专注于业务逻辑,而非底层实现...

    Hibernate+学习笔记

    同时,合理设计实体和表结构,避免 N+1 查询问题,也是优化的重要手段。 通过深入学习 Hibernate,开发者可以更高效地进行 Java 数据库开发,减少与数据库的交互复杂度,提高应用程序的可维护性和可扩展性。本学习...

    hibernate源码release-4.1.4.Final版

    包括但不限于:合理使用缓存,避免N+1查询问题,使用批处理更新,选择合适的主键生成策略,以及优化HQL和SQL查询等。 通过深入学习Hibernate 4.1.4.Final的源码,我们可以更好地理解其设计思想,提升我们的编程技巧...

    Hibernate part 14:查询及数据库并发事务

    10. **最佳实践**:包括合理设计实体关系、避免N+1查询、使用批处理等,都是提升并发性能的关键。 通过深入学习这些概念和技巧,开发者能更好地利用Hibernate进行高效、安全的数据库操作,并在并发环境下确保数据的...

    hibernate分页查询 数据库连接

    此外,优化查询,避免N+1查询问题,合理设计实体关系,都能有效提升Hibernate分页查询的效率。 总结起来,Hibernate的分页查询和数据库连接管理是其强大功能的重要组成部分。正确理解和使用这些特性,能够帮助...

    struts2+spring2+hibernate3.1 Hibernate帮助文档

    8. **懒加载和立即加载**:懒加载机制在对象初始化时并不加载关联的对象,而是在需要时才加载,有效避免了N+1查询问题。立即加载则是在获取对象时同时加载关联数据。 9. **一对多、多对一、一对一和多对多关系映射*...

    Hibernate二级缓存攻略

    然而,对于复杂的查询,如分页或条件查询,使用二级缓存可能仍然存在1+N问题,因为不同的查询条件可能无法充分利用缓存。 总的来说,Hibernate的二级缓存是提升应用性能的有效手段,但正确配置和使用缓存策略是至关...

    struts2+hibernate 用户注册管理系统

    5. **缓存机制**:通过一级缓存Session和二级缓存Region,提高数据读取效率,减少对数据库的访问压力。 **用户注册与管理** 1. **注册过程**:用户填写注册信息(如用户名、密码、邮箱等),Struts2 Action接收到...

    SSH框架整合spring4+hibernate4+struts2(终结版)

    此外,性能优化也是SSH项目中不可忽视的部分,如合理配置缓存、避免N+1查询等。 综上所述,SSH框架的整合是一项重要的技能,它涉及到了Java应用开发的多个层面,包括业务逻辑处理、数据持久化以及用户交互。理解并...

    spring+struts+hibernate整合实例

    针对数据库查询进行优化,如合理使用索引,避免N+1查询问题等。 总结,"spring+struts+hibernate"的整合项目展示了Java Web开发中的经典技术组合,通过Spring的管理、Struts2的请求处理和Hibernate的对象关系映射,...

Global site tag (gtag.js) - Google Analytics