框架技术

maven部分

依赖范围:
  compile :编译需要,测试需要、运行时也需要(会被打包),大部分的jar包都是这个范围
  provided :编译需要,测试需要,运行时不需要(不会被打包),例如servlet-api包
  test: 编译时不需要、测试时需要、运行时不需要(不会被打包),例如junit包
  runtime: 编译不需要,测试时需要,运行时需要(会被打包),例如:jdbc驱动包

Spring部分:
  主要学习Spring中的两大核心技术:IoC和AOP:
  IoC(Inverse of Control 反转控制): 将对象创建权利交给Spring工厂进行管理。
  AOP(Aspect Oriented Programming 面向切面编程),基于动态代理功能增强。

public class BeanFactory {
            //从xml中解析bean 通过反射得到对象存放到map集合中
            private static Map<String,Object> map=new HashMap<String,Object>();
            static{
                        SAXReader saxReader = new SAXReader();
                        try {
                                    //获取xml文件的document对象
                                    Document document = saxReader.read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
                                    Element root = document.getRootElement();
                                    String id = root.attributeValue("id");
                                    //得到类的全路径名称
                                    String clazz = root.attributeValue("class");
                                    //通过反射生成对象
                                    Object obj = Class.forName(clazz).newInstance();
                                    //存放到map集合中
                                    map.put(id, obj);
                        } catch (Exception e) {
                                    e.printStackTrace();
                        }
            }
            /**获取bean对象的工厂方法
             * @param id
             * @return
             */
            public static Object getBean(String id){
                        return map.get(id);
            }
}

spring依赖

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.2.4.RELEASE</version>
</dependency>

BeanFactory和ApplicationContext的区别
  BeanFactory – BeanFactory采取延迟加载,第一次getBean时才会初始化Bean
  ApplicationContext – 在加载applicationContext.xml时候就会创建具体的Bean对象的实例

scope属性代表Bean的作用范围:
  singleton:单例(默认值)
  prototype:多例
    在Spring框架整合Struts2框架的时候,Action类也需要交给Spring做管理,配置把Action类配置成多例!!
  init-method属性
    当bean被载入到容器的时候调用init-method属性指定的方法
  destroy-method属性
    当bean从容器中删除的时候调用destroy-method属性指定的方法
    想查看destroy-method的效果,有如下条件:
  scope= singleton有效
    web容器中会自动调用,但是main函数或测试用例需要手动调用(需要使用ClassPathXmlApplicationContext的close()方法)

Spring生成bean的三种方式:
无参构造方法
静态工厂实例化方法
  在配置DeptDaoImpl这个bean时,class属性写的不是DeptDaoImpl的全路径名,而是工厂类的全路径名;
factory-method:指定工厂类中静态方法的名字


public class Factory {
  public static DeptDao create(){
        System.out.println("调用了静态工厂方法");
         return new DeptDaoImpl();
    }
}
<bean id="deptDao" class="cn.itcast.factory.Factory" factory-method="create"></bean>

实例工厂实例化方法

public class Factory {
  public DeptDao create(){
        System.out.println("调用了静态工厂方法");
         return new DeptDaoImpl();
    }
}
<bean id="factory" class="cn.itcast.factory.Factory"></bean>
<bean id="deptDao" factory-bean="factory" factory-method="create"></bean>

IOC和DI的概念:
  IOC – Inverse of Control,控制反转,将对象的创建权反转给Spring!!
  DI – Dependency Injection,依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件中!!
  如果UserServiceImpl的实现类中有一个属性,那么使用Spring框架的IOC功能时,可以通过依赖注入把该属性的值传入进来!!

依赖注入:
  构造方法注入 需要提供有参的构造方法
  set方法注入 需要提供属性的set方法 不需要提供有参数的构造方法
  注意:采用set方法注入时,类中一定要有无参构造方法,因为spring会先调用无参构造方法实例化对象。
  通过set方法注入还有其它两种写法,这两种写法都是spring在新的版本中提供的写法:
  1、p命名空间的写法
  2、SpEL的写法


<!-- 构造方法注入 -->
<bean id="car" class="cn.itcast.domain.Car">
    <constructor-arg name="name" value="奥迪A6"></constructor-arg>
    <constructor-arg name="price" value="57.3"></constructor-arg>
</bean>
  <!-- 第二种注入形式:set方法注入 -->
<bean id="people" class="cn.itcast.domain.People">
     <property name="name" value="小明"></property>
     <property name="address" value="上海"></property>
     <property name="car" ref="car"></property>
</bean>
<bean id="people" class="cn.itcast.domain.People" p:name="小刚" p:address="北京" p:car-ref="car"></bean>
<bean id="people" class="cn.itcast.domain.People">
     <property name="name" value="#{'小明'}"></property>
     <property name="address" value="#{'上海'}"></property>
     <property name="car" value="#{car}"></property>
</bean>
<!-- SpEL的写法 -->
<bean id="carInfo" class="cn.itcast.domain.CarInfo"></bean>
     <bean id="car2" class="cn.itcast.domain.Car2">
     <property name="name" value="#{carInfo.name}"></property>
     <property name="price" value="#{carInfo.calculatePrice()}"></property>
</bean>

常用注解:

@Autowired
作用:
自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在
spring容器查找,找到了也可以注入成功。找不到就报错。

@Qualifer
作用:
在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
属性:value:指定bean的id。

@Resource
作用:直接按照Bean的id注入。它也只能注入其他bean类型。
   属性:name:指定bean的id。

注解小结:
用于创建bean对象的
@Component
@Controller
@Service
@Repository

与注入值相关
@Value
@Autowired
@Qualifier
@Resource

与范围相关的注解
@Scope

与生命周期相关的
@PostConstruct
@PreDestroy

Spring整合DBUtils


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="customerService" class="cn.itcast.service.impl.CustomerServiceImpl">
         <property name="customerDao" ref="customerDao"></property>
        </bean>
         <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl">
         <property name="queryRunner" ref="queryRunner"></property>
        </bean>
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
         <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
         <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hibernate"></property>
         <property name="user" value="root"></property>
         <property name="password" value="123456"></property>
        </bean>
</beans>

注解方法

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
        <context:component-scan base-package="cn.itcast"></context:component-scan>
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
             <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
             <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
             <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hibernate"></property>
             <property name="user" value="root"></property>
             <property name="password" value="123456"></property>
        </bean>
</beans>

AOP
  AOP的作用:在不修改源代码的情况下,可以实现功能的增强。
  AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

AOP的应用场景
 场景一: 记录日志
 场景二: 监控方法运行时间 (监控性能)
 场景三: 权限控制
 场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
 场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
那Spring中AOP是怎么实现的呢?Spring中AOP的有两种实现方式:
 1、JDK动态代理
 2、Cglib动态代理

  注意:JDK动态代理只能对实现了接口的类产生代理。

public class JdkProxy implements InvocationHandler{
  private CustomerDao customerDao;//要增强的目标
   public JdkProxy(CustomerDao customerDao) {
    this.customerDao = customerDao;
  }
/**
 * 利用jdk动态代理生成对象的方法
 * newProxyInstance的三个参数:
 * loader:目标类的类加载器
 * interfaces:目标类所实现的接口
 * handler:回调
 * @return
 */
public CustomerDao create(){
    CustomerDao proxy =  (CustomerDao) Proxy.newProxyInstance(customerDao.getClass().getClassLoader(),         customerDao.getClass().getInterfaces(),this);
    return proxy;
}

/**
 * 当你调用目标方法时,其实是调用该方法
 * invoke中的三个参数:
 * proxy:代理对象
 * method:目标方法
 * args:目标方法的形参
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //添加一个记录日志的功能
        System.out.println("记录日志...");
        //调用目标的方法
        Object result = method.invoke(customerDao, args);
        return result;
    }
}

@Test
public void test1(){
    //目标
    CustomerDao customerDao = new CustomerDaoImpl();
    JdkProxy jdkProxy = new JdkProxy(customerDao);
    CustomerDao proxy = (CustomerDao) jdkProxy.create();
    proxy.save();
}

  在实际开发中,可能需要对没有实现接口的类增强,用JDK动态代理的方式就没法实现。采用Cglib动态代理可以对没有实现接口的类产生代理,实际上是生成了目标类的子类来增强。
  首先,需要导入Cglib所需的jar包。提示:spring已经集成了cglib,我们已经导入了spring包,故不需要再导入其它包了。
创建LinkManDao类,没有实现任何接口

public class CglibProxy implements MethodInterceptor{
   private LinkManDao linkManDao;
    public CglibProxy(LinkManDao linkManDao) {
     this.linkManDao = linkManDao;
}
public LinkManDao create(){
    //创建cglib的核心对象
    Enhancer enhancer = new Enhancer();
    //设置父类
    enhancer.setSuperclass(linkManDao.getClass());
    //设置回调
    enhancer.setCallback(this);
    //创建代理对象
    LinkManDao proxy = (LinkManDao) enhancer.create();
    return proxy;
}

/**
 * 当你调用目标方法时,实质上是调用该方法
 * intercept四个参数:
 * proxy:代理对象
 * method:目标方法
 * args:目标方法的形参
 * methodProxy:代理方法
 */
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    //添加检测权限的功能
    System.out.println("记录日志...");
    //调用目标(父类)方法
    // Object result = method.invoke(linkManDao, args);
    Object result = methodProxy.invokeSuper(proxy, args);
    return result;
}
}

@Test
public void test2(){
    //目标
    LinkManDao linkManDao = new LinkManDao();
    CglibProxy cglibProxy = new CglibProxy(linkManDao);
    LinkManDao proxy = cglibProxy.create();
    proxy.save();
}

AOP开发,IOC的依赖不能少;同时引入AOP的依赖,坐标如下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.2.4.RELEASE</version>
</dependency>

       <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>
        <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>
        <!-- aop配置 -->
        <aop:config>
            <!-- 配置切入点 告诉spring 哪些方法需要被增强 -->
            <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pt1"/>
            <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.before(..))" id="pt2"/>
            <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))" id="pt3"/>
            <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))" id="pt4"/>
            <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))" id="pt5"/>
            <aop:aspect ref="myAspectXml">
                        <!-- 配置切面 告诉spring框架 调用切面类中的哪些方法来增强 -->
                        <aop:before method="writeLog" pointcut-ref="pt1"/>
                        <aop:before method="before" pointcut-ref="pt2" />
                        <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pt3"/>
                        <aop:around method="around" pointcut-ref="pt4" />
                        <aop:after-throwing method="afterthrowing" throwing="ex" pointcut-ref="pt5"/>
                        <aop:after method="after" pointcut-ref="pt5" />
            </aop:aspect>
        </aop:config>

注意,最终通知和后置通知的区别:最终通知,不管异常与否,都执行;而后置通知在异常时不执行。

在DAO中使用JdbcTemplate的两种方式

不使用注解方式:

        <!-- 配置bean引入jdbc.properties -->
        <!--
        <bean  class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
            <property name="location" value="classpath:jdbc.properties"></property>
        </bean> -->
        <!-- 通过context标签引入jdbc.properties -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">
            <!--
            <property name="jdbcTemplate" ref="jdbcTemplate"></property> -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!--
        <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean> -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driverClass}"></property>
            <property name="jdbcUrl" value="${jdbc.url}"></property>
            <property name="user" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
       </bean>

使用注解的方式

        <!-- 通过context标签引入jdbc.properties -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driverClass}"></property>
            <property name="jdbcUrl" value="${jdbc.url}"></property>
            <property name="user" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
       </bean>

public class AccountRowMapper implements RowMapper<Account>{
    /**
     * 如何把账户表里的一行数据形成账户对象
     */
    @Override
    public Account mapRow(ResultSet rs, int row) throws SQLException {
        Account account = new Account();
        account.setId(rs.getLong("id"));
        account.setName(rs.getString("name"));
        account.setMoney(rs.getDouble("money"));
        return account;
    }
}

思考:
两版Dao有什么区别呢?
答案:
  第一种在Dao类中定义JdbcTemplate的方式,适用于所有配置方式(xml和注解都可以)。
  第二种让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了。

Spring中的事务控制
事务隔离级反映事务提交并发访问时的处理态度
  ISOLATION_DEFAULT 默认级别 归属下列某一种
  ISOLATION_READ_UNCOMMITTED 可以读取未提交的数据
  ISOLATION_READ_COMMITTED 只能读取已提交数据 解决脏读问题(Oracle默认级别)
  ISOLATION_REPEATABLE_READ 是否读取其他事务提交修改后的数据 解决不可重复读问题(mysql默认级别)
  ISOLATION_SERIALIZABLE 是否读取其他事务提交添加后的数据 解决幻影读问题

事务的传播行为
  REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。

Spring声明式事务控制的采用的是AOP思想,所以需要引入与AOP相关的依赖。
总共需要引入的依赖有:
  ioc的依赖;
  aop的依赖
  jdbc模板+tx+c3p0的依赖
  spring整合junit单元测试的依赖

基于xml的声明式事务控制

                        <!-- 配置事务管理器 -->
                        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                                    <property name="dataSource" ref="dataSource"></property>
                        </bean>
                        <!--
                                    配置事务的属性
                                    id:指定advice的id,后边要用到
                                    transaction-manager:写的是事务管理器的id
                         -->
                        <tx:advice id="txAdvice" transaction-manager="transactionManager">
                                    <tx:attributes>
                                                <tx:method name="find*" read-only="true"/>
                                                <tx:method name="*"/>
                                    </tx:attributes>
                        </tx:advice>
                        <!-- 配置事务切面 -->
                        <aop:config>
                                    <!-- 配置切入点表达式 告诉框架哪些方法要控制事务 -->
                                    <aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/>
                                    <!-- 将定义好的事务属性应用到切入点 -->
                                    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
                        </aop:config>

因为配置了查询方法只读事务所以做增删改操作会报错

Connection is read-only. Queries leading to data modification are not allowed

          @Override
            public Account findById(Long id) {
                        Account account = accountDao.findById(id);
                        /*
                         * 应该是只能做查询操作,但现在做了修改操作,是不被允许的.
                         * 怎么才能不允许在查询方法中,做增删改操作呢?给该方法加只读事务
                         */
                        account.setMoney(100000.0);
                        accountDao.update(account);
                        return account;
            }

基于注解的声明式事务控制

        <!-- 开启spring注解扫描 -->
        <context:component-scan base-package="cn.itcast"></context:component-scan>

        <!--
            开启spring事务注解支持
            transaction-manager:写事务管理器的id
        -->
        <tx:annotation-driven transaction-manager="transactionManager"/>

        <!-- 通过context标签引入jdbc.properties -->
        <context:property-placeholder location="classpath:jdbc.properties"/>

        <!-- 采用注解方式 dao实现没有继承JdbcDaoTemplate 无法给jdbcTemplate加上注解-->
        <!-- 注意:采用注解来配置,一定要显示定义JdbcTemplate,因为在dao中如要注入JdbcTemplate -->
        <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driverClass}"></property>
            <property name="jdbcUrl" value="${jdbc.url}"></property>
            <property name="user" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
        </bean>

        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"></property>
        </bean>

在业务层加@Transactional注解
  在find开头的方法加只读事务 @Transactional(readOnly=true)//配置只读事务

MyBatis部分
  Mapper接口开发方法只需要程序员编写Mapper接口(相当于dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象(相当于dao实现类),然后可以通过该代理对象进行增删改查的操作。区别是传统的编写方式dao的实现类我们需要自己创建和编写,而使用Mybatis框架的动态代理方式,我们只需要编写接口,实现类由Mabits为我们自动生成(即动态代理对象)。
  
Mapper接口的动态代理实现,需要遵循以下规范:
  1. 映射文件中的命名空间与Mapper接口的全路径一致
  2. 映射文件中的statement的Id与Mapper接口的方法名保持一致
  3. 映射文件中的statement的ResultType必须和mapper接口方法的返回类型一致(即使不采用动态代理,也要一致)
  4. 映射文件中的statement的parameterType必须和mapper接口方法的参数类型一致(不一定,该参数可省略)

问题:发现在测试的时候,查询操作都没有什么问题,但是增删改没有反应。
  原因:原来在UserDaoImpl中我们是手动提交事务的,但是用来动态代理生成的代理对象的话,并没有提交事务。
解决方案:设置为自动提交即可。

//获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);

思考:接口中是否可以有重载的方法?
  注意:由于使用的是接口方式进行查询(动态代理),而其它要求映射文件中的statement的Id与Mapper接口的方法名保持一致。而statement的id由于是唯一的不能重复,因此也就意味着接口中的方法也不能重名,那也就是说不能有重载的方法了。

在mybatis-config.xml配置mapper接口的全路径:
  这种配置方式,在mybatis-config.xml配置文件中配置了mapper接口的全路径,并没有配置mapper接口的映射文件的位置。如果要让mybatis找到对应的映射文件,则必须满足一定的条件或规则:
 1、映射文件和mapper接口在同一个目录下
 2、文件名必须一致
 3、映射文件的namespace必须和mapper接口的全路径保持一致

parameterType传入参数
 CRUD标签都有一个属性parameterType,statement通过它指定接收的参数类型。

接收参数的方式有两种:
 1、#{}预编译
 2、${}非预编译(直接的sql拼接,不能防止sql注入)

参数类型有三种:
 1、基本数据类型
 2、HashMap(使用方式和pojo类似)
 3、Pojo自定义包装类型

${}的用法
 场景:数据库有两个一模一样的表。历史表,当前表
 查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。

  注意:使用 使 {value}获取参数值,而#{} 只是表示占位,与参数的名字无关,如果只有一个参数,可以使用任意参数名接收参数值,会自动对应。但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名
  在分表存储的情况下 我们从哪张表查询是不确定的,也就是说sql语句不能写死 表名是动态的 查询条件是固定的 比如 select * from ${tableName} where id=#{id}
  
 #{}的用法
  #{}类似于sql语句中的?,但是单个参数和多个参数的使用方式稍微有点区别。

单个参数
 在一个参数的情况下#{id}中的id可以修改成任意字符。

多个参数
 当mapper接口要传递多个参数时,有两种传递参数的方法:
 1. 默认规则获取参数{0,1,param1,param2}
 2. 使用@Param注解指定参数名

注意:
 单个参数时,#{}与参数名无关的。
 多个参数时,#{} ${}与参数名(@Param)有关。

什么时候需要加@Param注解?什么时候不加?
  单个参数不加,多个参数加
  终极解决方案:都加。

面试题(# $区别)
  练习:根据用户名进行模糊查询
  注意:如果是单引号 #{userName}前后需要有空格 如果是双引号,加不加空格都可以:

hashMap这种类型的参数怎么传递参数呢?
  其实,它的使用方式和pojo有点类似,简单类型通过#{key}或者 k e y {key.属性名}或者#{key.属性名}

         /**使用map类型参数传递
             * @param map
             * @return
             */
            public User loginMap(Map<String,Object> map);

            <!--
                        获取HashMap中的参数:
                        简单对象:#{key}      或者 ${key}
                        pojo对象:#{key.属性名} 或者  ${key.属性名}
             -->
            <select id="loginMap" resultType="User">
                        select * from tb_user where user_name=#{username} and password=#{password}
            </select>

            @Test
            public void testLoginMap(){
                        Map<String,Object> map=new HashMap<String,Object>();
                        map.put("username", "zhangsan");
                        map.put("password", "123456");
                        User user = userMapper.loginMap(map);
                        System.out.println(user);
            }

ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决两大问题:
  POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式,比如id和userId)
完成高级查询,比如说,一对一、一对多、多对多

解决列名和属性名不一致
  解决方案1:在sql语句中使用别名
  解决方案2:参考驼峰匹配 — mybatis-config.xml 的时候

    <settings>
            <setting name="mapUnderscoreToCamelCase" value="true" />
        </settings>

  解决方案3:resultMap自定义映射

resultMap中,主键需要通过id子标签配置,表字段和属性名不一致的普通字段需要通过result子标签配置。
那么,字段和属性名称都匹配的字段要不要配置?
  这个取决于resultMap中的autoMapping属性的值:
   为true时:resultMap中的没有配置的字段会自动对应。如果不配置,则默认为true。
   为false时:只针对resultMap中已经配置的字段作映射。

Crud标签的使用:
  Insert标签可以设置id回显
  userGeneratedKeys:开启主键回显,true
  keyColunm:表中主键的字段名称
  keyproperty::对象中主键对应的属性名称

  sql片段:可以抽取相同的sql片段,方便进行重复调用,如果是将抽取的片段单独的存入一个xml中,该xml要受mybatis-config.xml管理。

动态sql

            <select id="queryUserListByUserNameAndAge" resultType="User">
                        select * from tb_user
                        <!--
                                    where标签的作用:
                                                1          自动添加where关键字
                                                2  自动去除动态sql前面多余的一个and或者or
                         -->
                        <where>
                                    <if test="username!=null and username.trim()!=''">
                                                 user_name like "%"#{username}"%"
                                    </if>
                                    <if test="age!=null">
                                                 and age > #{age}
                                    </if>
                        </where>
            </select>

            <update id="updateUserSelective">
                        UPDATE `tb_user`
                        <!--
                                    set标签 可以将动态sql语句最后多余的逗号去除
                         -->
                        <set>
                                    <if test="userName!=null and userName.trim()!=''">
                                     `user_name` = #{userName},
                                    </if>
                                    <if test="age!=null">
                                     `age` = #{age},
                                    </if>
                        </set>
                        WHERE
                                    `id` = #{id};
            </update>

            <select id="queryUserListByIds" resultType="User">
                        select * from tb_user
                        <where>
                                    <if test="ids!=null"> id in
                                                <foreach collection="ids" item="id" open="(" close=")" separator=",">
                                                            #{id}
                                                </foreach>
                                    </if>
                        </where>
            </select>

一级缓存
  在mybatis中,一级缓存默认是开启的,并且一直无法关闭(不管你用或不用,她一直都在),作用域:在同一个sqlSession下

    @Test
            public void testFirstCache(){
                        User user = userMapper.queryUserById(1L);
                        System.out.println(user);
                        System.out.println("==============");
                        sqlSession.clearCache();//清空一级缓存
                        User user2 = userMapper.queryUserById(1L);
                        System.out.println(user2);
            }

            @Test
            public void testFirstCache2(){
                        User user1 = userMapper.queryUserById(1L);
                        System.out.println(user1);
                        User user = new User();
                        user.setId(2l);//即使修改的其他的记录,也会将一级缓存中的数据进行清空。
                        user.setUserName("张无忌");
                        user.setPassword("4321");
                        user.setName("zhangwuji");
                        //不设置age
                        userMapper.updateUserSelective(user);
                        System.out.println("==============");
                        User user2 = userMapper.queryUserById(1L);
                        System.out.println(user2);
            }

  由于insert、update、delete会清空缓存,所以第二次查询时,依然会输出sql语句,即从数据库中查询。
  当我们在一个网站浏览商品的时候,一旦对一个商品进行下单,那么所有的一级缓存就都被被清空。
  因此mybatis自带的一级缓存其实并没有什么作用。

二级缓存
mybatis 的二级缓存的作用域:

  1. 同一个mapper的namespace,在同一个namespace中查询sql可以从缓存中命中。
  2. 跨sqlSession,不同的SqlSession可以从二级缓存中命中

怎么开启二级缓存:

  1. 在映射文件中,添加<cache />标签
  2. 在全局配置文件中,设置cacheEnabled参数,默认已开启。

注意:
  由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,因此在测试二级缓存时必须先将第一个sqlSession关闭
  二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。

  二级缓存在执行update、insert、delete的时候,也同样会清空二级缓存中的内容。


@Test
public void testSecondCache(){
    User user1 = userMapper.queryUserById(21l);
    System.out.println(user1);
    sqlSession.close();//关闭sqlSession,此时会将数据存入二级缓存中。
    //获取第二个sqlSession
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    //通过第二个sqlSession中的接口执行查询
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    //在第二个sqlSession中执行更新
    System.out.println("============执行更新============");
    User user = new User();
    user.setId(21l);//即使修改的其他的记录,也会将一级缓存中的数据进行清空。
    user.setUserName("张翠山");
    user.setPassword("4321");
    user.setName("zhang翠山");
    //不设置age
    userMapper2.updateUserSelective(user);
    System.out.println("===============跨sqlSession查询================");
    User user2 = userMapper2.queryUserById(21l);
    System.out.println(user2);
}

补充:
serialVersionUID的作用:
  相当于设置了一个版本,.class文件中记录一个版本号,序列化出来的文件中有一个版本号,当修改对象中某个属性的时候,会检查两个版本号是否一直。
  如果设置了该属性,那么一旦在修改对象中的某个属性后,版本号发生改变,一旦检查出版本号不一致,就不能再次序列化了。
  如果不设置每次修改数据都可以序列化。

多对多查询示例

<resultMap type="Order" id="orderUserDetailItemMap" autoMapping="true">
    <id property="id" column="id"/>
    <!-- 对一查询User -->
    <association property="user" javaType="User" autoMapping="true">
        <!--
            property="id":关联对象的主键的属性名称
            column:关联对象在当前表中的外键的字段名称,如果重复,可以使用别名或者外键名称
         -->
        <id property="id" column="uid"/>
    </association>
    <!-- 对多查询Orderdetail -->
    <collection property="detailList" javaType="List" ofType="Orderdetail" autoMapping="true">
        <!--
            property="id":关联对象的主键的属性名称
            column:关联对象在表中的主键名称,如果重复,可以使用别名
         -->
        <id property="id" column="detail_id"/>
        <!-- 通过Orderdetail对一查询Item -->
        <association property="item" javaType="Item" autoMapping="true">
            <!--
                property:item的主键的属性名称
                column:表中主键的字段名称,如果重名,可以使用别名,或者外键名称
             -->
            <id property="id" column="iid"/>
        </association>
    </collection>
</resultMap>
<select id="queryOrderAndUserAndOrderdetailAndItemByOrderNumber" resultMap="orderUserDetailItemMap">
        select *,od.id as detail_id,u.id as uid,i.id as iid from tb_order o
        inner join tb_user u on o.user_id = u.id
        inner join tb_orderdetail od on o.id = od.order_id
        inner join tb_item i on od.item_id = i.id
        where o.order_number = #{number}
</select>

高级查询的整理
  resutlType无法帮助我们自动的去完成映射,所以只有使用resultMap手动的进行映射
resultMap:
  type 结果集对应的数据类型
  id 唯一标识,被引用的时候,进行指定
  autoMapping 开启自动映射
  extends 继承
  子标签:
    association:配置对一的映射
    property 定义对象的属性名
javaType 属性的类型
  autoMapping 开启自动映射
  collection:配置对多的映射
  property 定义对象的属性名
  javaType 集合的类型
  ofType 集合中的元素类型
  autoMapping 开启自动映射

延迟加载

<resultMap type="Order" id="orderUserLazyMap" autoMapping="true">
    <id property="id" column="id"/>
    <!-- 关联查询user
        select:延迟加载user,通过调用另外的user的statement来发送第二个语句查询user
        column:查询user的参数,该id的信息其实是在查询出来的order中的user_id
           如果参数有多个,可以使用{user_id=id,user_name = userName}的方式来传参
    -->
    <association property="user" javaType="User" select="queryUserByUserIdOfOrder" column="user_id"></association>
</resultMap>

<!-- 通过order_number查询order -->
<select id="queryOrderUserLazy" resultMap="orderUserLazyMap">
    select * from tb_order where order_number = #{orderNumber}
</select>
<!-- 通过order中的user_id查询user -->
<select id="queryUserByUserIdOfOrder" resultType="User">
    select * from tb_user where id = #{id}
</select>

<settings>
    <!-- 关闭二级缓存,默认是开启,false:关闭 -->
    <setting name="cacheEnabled" value="false"/>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

需要cglib的动态代理的jar包
  在xml中有5个预定义的实体引用
  1、使用xml中的字符实体
  2、使用CDATA 内部的所有东西都会被解析器忽略,因此只要将字符实体编写在其中,就不需要进行转义。

SpringMVC部分
 标准URL映射
    Ant风格的映射:(0个或多个字符)、?(单个字符)、*(0个或多个路径)
    Rest风格的映射:占位符 注意:占位符的主要应用是用来接收参数
    限定请求方法的映射:get、post、put、delete
    限定参数的映射:限定哪些请求参数可以访问

  接收数据及数据绑定
    1. 接收servlet的内置对象
    2. 接收占位符请求路径中的参数
    3. 接收普通的请求参数
    4. 获取cookie参数
    5. 基本数据类型的绑定
    6. Pojo对象的绑定
    7. 集合的绑定

          @RequestMapping(value="show21")
            public String test21(Model model,HttpServletRequest req){
                        Cookie[] cookies = req.getCookies();
                        if(cookies!=null){
                                    for (Cookie cookie : cookies) {
                                                if(cookie.getName().equalsIgnoreCase("jsessionid")){
                                                            model.addAttribute("msg", "jsessionid:"+cookie.getValue());
                                                }
                                    }
                        }
                        return "hello";
            }

            @RequestMapping(value="show22")
            public String test22(Model model,@CookieValue(value="JSESSIONID",defaultValue="101")String jsessionid){
                        model.addAttribute("msg", "jsessionid:"+jsessionid);
                        return "hello";
            }

基本数据类型的绑定
  字符串、整型、浮点型、布尔型、数组。
  需求:通过一个页面提交某些数据,由自定义处理器获取这些数据并打印到控制台,但是不需要响应任何页面。
  
  @ResponseStatus(value=HttpStatus.OK):如果不响应页面,就需要响应状态。
  注意:如果不设置状态码:即不配置注解    
  
  @ResponseStatus(value=HttpStatus.OK)就会报404。
  响应状态HttpStatus是一个枚举类

          @RequestMapping("show23")
           @ResponseStatus(HttpStatus.OK)
            public void test23(@RequestParam("name")String name,
                                    @RequestParam("age")Integer age,
                                    @RequestParam("isMarry")Boolean isMarry, //可以将on或者1转换为true,0转换为false.
                                    @RequestParam("income")Float income,
                                    @RequestParam("interests")String[] interests) {

                        StringBuffer sb = new StringBuffer();
                        sb.append("name:"+name+"\n");
                        sb.append("age:"+age+"\n");
                        sb.append("isMarry:"+isMarry+"\n");
                        sb.append("income:"+income+"\n");
                        sb.append("interests:[");
                        for (String inter : interests) {
                                    sb.append(inter+" ");
                        }
                        sb.append("]");
                        System.out.println(sb.toString());
            }

关于提交的中文乱码问题的处理:
解决方案:
    1、可以配置一个全站乱码过滤器。
    2、手动配置post请求编码过滤器

POJO对象的绑定
  SpringMVC会将请求参数名和POJO实体中的属性名(set方法)进行自动匹配,如果名称一致,将把值填充到对象属性中,并且支持级联(例如:user.dept.id)。

            @RequestMapping("show24")
            public String test24(Model model,User user,@RequestParam("name")String name){
                        model.addAttribute("msg", user+"name:"+name);
                        return "hello";
            }

集合的绑定
  如果List中封装的是pojo对象,不能够直接在方法中形参中使用List,需要将List对象包装到一个类中才能绑定
  要求:表单中input标签的name属性的值和集合中元素的属性名一致。

创建一个UserVo来包装List集合

public class UserVo {
            private List<User> users;
            public List<User> getUsers() {
                        return users;
            }
            public void setUsers(List<User> users) {
                        this.users = users;
            }
            @Override
            public String toString() {
                        return "UserVo [users=" + users + "]";
            }

}

            @RequestMapping("show25")
            public String test25(Model model,UserVo userVo){
                        model.addAttribute("msg", "打印参数:"+userVo.toString());
                        return "hello";
            }


            <form action="/hello/show25.do" method="post">
                        name0:<input type="text" name="users[0].name" /><br />
                        name1:<input type="text" name="users[1].name" /><br />
                        <input type="submit" value="提交" />
            </form>

如果集合中的元素为基本数据类型 那么可以直接绑定

            @RequestMapping("show26")
            public String test26(Model model,@RequestParam("ids")List<Long> ids){
                        model.addAttribute("msg", "打印参数:"+ids.toString());
                        return "hello";
            }

http://localhost:8080/hello/show26.do?ids=101,102
http://localhost:8080/hello/show26.do?ids=101&ids=102

接收数据和数据绑定部分总结
   代码优化:直接编写一个String返回值类型的方法,形参为Model。
返回值默认为视图名称,model可以设置数据。
  获取常用的servlet内置对象
    直接在形参中设置HttpServletRequest、HttpServletResponse、HttpSession用来接收。
  占位符:@PathVariable(value=”xx”)
    value属性不能省略:之所以属性省略也可以绑定参数,那是因为eclipse工具帮助我们在编译成class文件时自动添加上去的,一旦换了其他工具,可能时不行了,因此不要省略属性。
  接收普通的请求参数:@RequsetParam
    value属性:参数名称
    required属性:参数是否必须,默认为true
    defaultValue属性:设置参数默认值,一旦设置该属性,required属性自动失效

获取cookie:
  直接在形参中通过@CookieValue注解获取cookie中的数据,其属性使用方式和@RequestParam相同
基本数据类型的绑定:
  直接在形参中通过@RequestParam注解接收即可
Pojo对象的绑定:
  直接在形参中定义POJO类型的对象即可自动封装
集合的绑定:
  1. 元素为基本类型:可以直接通过@RequestParam注解在形参中接收
  2. 元素为pojo类型:需要对其进行包装。

SpringMVC和Struts2的比较
 1.SpringMVC的入口是Servlet Struts2的入口是Filter 两者的实现机制不同
 2.SpringMVC基于方法设计,传递参数时通过方法形参,其实现是单例模式(也可以改为多例 推荐用单例)
  Struts2基于类设计 传递参数是通过类的属性 只能是多例实现 性能上SpringMVC更高一些
 3.参数传递方面 Struts2是用类的属性接收的 也就是在多个方法间能共享

关于接受日期格式数据的原理
  如果接收的格式是2018/5/3 那么不需要加日期相关的注解 如果接收的是 2018-5-3 格式 那么需要在方法或者属性上加上@DateTimeFormat注解

            @RequestMapping("show36")
            public String test36(Model model,@RequestParam("date")@DateTimeFormat(pattern="yyyy-MM-dd")Date date){
                        model.addAttribute("msg", date);
                        return "hello";
            }

  单例:在使用的过程中,一直是一个对象。只被创建一次
  多例:会被创建多次。
  Struts多例是因为传参是通过类的属性,类可以被多个方法引用,一旦设置为单例,那么在获取对象中的数据时,容易获取的被修改后的数据,会产生安全问题。
  Springmvc是单例,是因为参数是通过方法传递的,不能被其他方法调用,一旦方法执行完毕就被释放掉了,不会有安全问题。
  单例模式是spring推荐的配置,它在高并发下能极大的节省资源,提高服务抗压能力。

JSON
  在实际开发过程中,json是最为常见的一种方式(本质上就是字符串),所以springmvc提供了一种更为简便的方式传递数据。
  @ResponseBody 是把Controller方法返回值转化为JSON,称为序列化
  @RequestBody 是把接收到的JSON数据转化为Pojo对象,称为反序列化
  
转换json的常用方式:
Gson Google的工具,功能强大,但是效率稍逊。
Fastjson 阿里的工具,效率高,但是功能稍逊。
jackson springmvc内置的转换工具,功能和效率都有不俗的表现,在世界范围内使用较广。
 
 

            @RequestMapping("show28")
            @ResponseBody     //将数据响应成json字符串
            public List<User> test28(){
                        List<User> users = new ArrayList<User>();
                        for (int i = 0; i < 20; i++) {
                                    User user = new User();
                                    user.setId(i+0L);
                                    user.setUsername("zhangsan"+i);
                                    user.setName("张三"+i);
                                    user.setAge(18);
                                    users.add(user);
                        }
                        return users;
            }

       /**自动将json字符串序列化user对象
             * @param model
             * @param user
             * @return
             */
            @RequestMapping("show29")
            public String test29(Model model,@RequestBody()User user){
                        model.addAttribute("msg", user);
                        return "hello";
            }

            /**问题:前台发送json格式的数据,后台能否执行使用String类型来接收?
                        可以:因为json本质上就是一个字符串。
             * @param model
             * @param user
             * @return
             */
            @RequestMapping("show30")
            public String test30(Model model,@RequestBody()String user){
                        model.addAttribute("msg", user);
                        return "hello";
            }

测试test30发现中文乱码
  原因:使用的消息转换器换成了StringHttpMessageConverter

解决方案:
  1、可以配置一个全站乱码过滤器。
  2、手动修改消息转换器中的编码集

这里我们使用第二种方式:手动修改消息转换器中的编码集
将注解驱动修改如下:设置String的消息转换器
该消息转换器中有一个构造函数可以设置编码集,因此只要直接赋值即可。

<mvc:annotation-driven>
     <mvc:message-converters>
     <bean class="org.springframework.http.converter.StringHttpMessageConverter">
     <constructor-arg index="0" value="UTF-8"></constructor-arg>
     </bean>
     </mvc:message-converters>
</mvc:annotation-driven>

@RestController
  有时如果在一个Contoller中所有的方法都是用来响应json格式数据的,那么如果有多个方法,就需要在多个方法上使用@ResponseBody,这样太麻烦,springmvc提供了一个@RestController,将该注解使用在Controller类上,那么该controller中的所有方法都默认是响应json格式的数据了。

文件上传
  SpringMVC的文件上传,底层也是使用的Apache的Commons-fileupload
  Spring有两个web相关的包:spring-webmvc-4.3.13.RELEASE.jar 该包中存放的springmvc的核心功能
  Spring-web-4.3.13.RELEASE.jar 该包中存放的是web的一些通用功能:比如监听器ContextLoaderListener以及文件上传解析器CommonsMultipartResolver等, 而文件上传解析器就需要fileupload的支持。

<!-- 文件上传 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

        /**文件上传 需要使用MultipartFile类型来接收数据
             * @param file
             * @return
             * @throws Exception
             */
            @RequestMapping("show31")
            public String test31(@RequestParam("file")MultipartFile file) throws Exception{
                        if(file!=null){
                                    file.transferTo(new File("c://upload//"+file.getOriginalFilename()));
                        }
                        return "redirect:/success.html";
            }

        /**自定义处理器异常解析器
         */
            public class MyHandlerExceptionResolver implements HandlerExceptionResolver{
                @Override
                public ModelAndView resolveException(HttpServletRequest request,
                                    HttpServletResponse response, Object handler, Exception ex) {
                        ModelAndView mv = new ModelAndView();
                        //对文件大小进行异常限制
                        if(ex instanceof MaxUploadSizeExceededException){
                                    mv.setViewName("hello");
                                    mv.addObject("msg", "文件上传大小超出限制");
                        }
                        return mv;
            }
}
        <!-- 配置文件上传解析器 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
                                     <!--
                                                maxUploadSize:默认单位是字节
                                                设置为5m:5*1024*1024=5242880 -->
                 <property name="maxUploadSize" value="5242880"></property>
                 <property name="defaultEncoding" value="utf-8"></property>
        </bean>
        <!-- 注册处理器异常解析器 -->
        <bean class="cn.itcast.exception.MyHandlerExceptionResolver"></bean>

转发以及重定向(forward redirect)

采用绝对路径写法

            @RequestMapping("hello")
            @Controller
            public class Hello2Controller {

            }            
            @RequestMapping("show32")
            public String test32(){
                        return "forward:/hello/show34.do?type=forward&id=101";
            }

            @RequestMapping("show33")
            public String test33(){
                        return "redirect:/hello/show34.do?type=redirect&id=101";
            }

            @RequestMapping("show34")
            public String test34(Model model,@RequestParam("type")String type,@RequestParam("id")Integer id){
                        model.addAttribute("msg", "是转发还是重定向?"+id+"..."+type);
                        return "hello";
            }

//采用相对路径写法

            @RequestMapping("show32")
            public String test32(){
                        return "forward:show34.do?type=forward&id=101";
            }

            @RequestMapping("show33")
            public String test33(){
                        return "redirect:show34.do?type=redirect&id=101";
            }

            @RequestMapping("show34")
            public String test34(Model model,@RequestParam("type")String type,@RequestParam("id")Integer id){
                        model.addAttribute("msg", "是转发还是重定向?"+id+"..."+type);
                        return "hello";
            }

拦截器
  HandlerExecutionChain是一个执行链,当请求到达DispatchServlet时,  DispatchServlet根据请求路径到HandlerMapping查询具体的Handler,从  HandlerMapping返回执行链给DispatcherServlet,其中包含了一个具体的Handler对象和Interceptors(拦截器集合)。
  
如何自定义拦截器:
springmvc的拦截器接口(HandlerInterceptor)定义了三个方法:
  preHandle调用Handler之前执行,称为前置方法
   返回值:true表示放行,后续业务逻辑继续执行
   false表示被拦截,后续业务逻辑不再执行,但之前返回true的拦截器的完成方法会倒序执行
  postHandle调用Handler之后执行,称为后置方法
  afterCompletion视图渲染完成之后执行

结论:拦截器的前置方法依次执行,
  后置方法和完成方法倒续执行
  当前置方法返回false时,后续的拦截器以及Handler方法不再执行,但它前序的前置方法返回true的拦截器的完成方法会倒序执行。
  完成方法会在视图渲染完成之后才去执行。
  

        <!-- 配置自定义拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
            <!-- 拦截所有的请求 -->
                        <mvc:mapping path="/**"/>
                        <bean class="cn.itcast.interceptor.MyInterceptor1"></bean>
            </mvc:interceptor>

            <mvc:interceptor>
            <!-- 拦截所有的请求 -->
                        <mvc:mapping path="/**"/>
                        <bean class="cn.itcast.interceptor.MyInterceptor2"></bean>
            </mvc:interceptor>
        </mvc:interceptors>

usermanage综合练习部分
ssm相关的依赖

              <dependency>
                                <groupId>org.springframework</groupId>
                                <artifactId>spring-webmvc</artifactId>
                    </dependency>
                    <dependency>
                                <groupId>org.springframework</groupId>
                                <artifactId>spring-jdbc</artifactId>
                    </dependency>
                    <dependency>
                                <groupId>org.springframework</groupId>
                                <artifactId>spring-aspects</artifactId>
                    </dependency>
                    <!-- Mybatis -->
                    <dependency>
                                <groupId>org.mybatis</groupId>
                                <artifactId>mybatis</artifactId>
                    </dependency>
                    <dependency>
                                <groupId>org.mybatis</groupId>
                                <artifactId>mybatis-spring</artifactId>
                    </dependency>
                        web.xml中配置post请求的编码过滤器
         <filter>
                    <filter-name>encodingFilter</filter-name>
                      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
                    <init-param>
                                <param-name>encoding</param-name>
                                <param-value>utf-8</param-value>
                    </init-param>
          </filter>
          <filter-mapping>
                    <filter-name>encodingFilter</filter-name>
                    <url-pattern>/*</url-pattern>
          </filter-mapping>

在web.xml中配置Spring相关配置
        <!-- 配置初始化spring容器的监听器 -->

        <context-param>
                    <param-name>contextConfigLocation</param-name>
                     <param-value>classpath:spring/applicationContext*.xml</param-value>
         </context-param>
        <listener>
                    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>

在web.xml中配置springmvc的相关配置

                    <!-- 配置springmvc的DispatcherServlet -->
         <servlet>
                    <servlet-name>usermanager</servlet-name>
                     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                    <init-param>
                                <param-name>contextConfigLocation</param-name>
                                <param-value>classpath:spring/usermanager-servlet.xml</param-value>
                    </init-param>
                    <load-on-startup>1</load-on-startup>
         </servlet>
         <servlet-mapping>
                    <servlet-name>usermanager</servlet-name>
                    <url-pattern>/</url-pattern>
         </servlet-mapping>

编写SpringMVC的配置文件usermanager-servlet.xml和测试Controller并完成springmvc的测试












测试正常 需要完成Mybatis独立测试
  创建pojo对象
  创建mapper接口
  创建UserMapper.xml配置文件
  创建mybatis-config.xml映射文件
  添加jdbc.properties配置文件
  测试

测试通过 进行Spring和mybatis整合
  在applicationContext-mybatis.xml文件中整合  通过不断地优化mybatis-config.xml中的配置 最终形成mybatis和spring的整合的配置文件如下:
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property  name="configLocation"  value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="dataSource" ref="dataSource"></property>
        <!-- mapperLocation配置mapper接口配置文件的路径
        value:配置映射文件路径,只要是在根目录下的mybatis中的mappers目录下的任意多级目录(0个或多个)下的任意一个映射文件都进行扫描
        -->
        <property name="mapperLocations"  value="classpath:mybatis/mapper/**/*.xml"/>
        <!-- 配置别名扫描的包 -->
        <property name="typeAliasesPackage" value="cn.itcast.usermanager.pojo"></property>
    </bean>
    <bean id="dataSource"  class="com.alibaba.druid.pool.DruidDataSource">
                    <property name="driverClassName" value="${jdbc.driverClass}"/>
                    <property name="url" value="${jdbc.url}"/>
                     <property name="username" value="${jdbc.username}"/>
                     <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--
        location:加载资源文件
        ignore-resource-not-found:找不到资源文件就忽略
        system-properties-mode:不读取电脑环境中变量
     -->
    <context:property-placeholder location="classpath:jdbc.properties" ignore-resource-not-found="false" system-properties-mode="FALLBACK"/>
    <!-- 优化单个mapper接口 -->
    <!--
    <bean id="userMapper"    class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface"  value="cn.itcast.usermanager.mapper.UserMapper"></property>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean> -->
    <!-- 配置mapper接口扫描 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 配置mapper接口所在的包路径 -->
        <property name="basePackage" value="cn.itcast.usermanager.mapper"></property>
    </bean>

分页插件原理
  Mybatis提供了plugin机制 允许我们在Mybatis的原有处理流程上加入自己逻辑,所以就可以加上我们的分页逻辑 也就是实现拦截器
  Mybatis支持的拦截接口有4个,Executor    ParameterHandler    ResultSetHandler    StatementHandler
  
分页插件的使用
注意:easyUI分页的请求参数名称为page rows 
返回的json数据必须包含key 为 total rows
                        <!-- 分页插件 -->
                        <dependency>
                                    <groupId>com.github.pagehelper</groupId>
                                    <artifactId>pagehelper</artifactId>
                                    <version>3.7.5</version>
                        </dependency>
                        <dependency>
                                    <groupId>com.github.jsqlparser</groupId>
                                    <artifactId>jsqlparser</artifactId>
                                    <version>0.9.1</version>
                        </dependency>

            <plugins>
                <!-- com.github.pagehelper为PageHelper类所在包名 -->
                <plugin interceptor="com.github.pagehelper.PageHelper">
                    <property name="dialect" value="mysql"/>
                </plugin>
            </plugins>

    @Override
    public EasyUIResult queryEasyUIResult(Integer page, Integer rows) {
        //调用分页插件提供的静态方法,page:页码,rows:页面大小
        PageHelper.startPage(page, rows);
        //查询所有用户信息
        List<User> list = userMapper.queryUserAll();
        //利用分页插件的方法获取总记录数
        PageInfo<User> pageInfo = new PageInfo<User>(list);
        //封装EasyUIResult
        EasyUIResult result = new EasyUIResult();
        result.setTotal(pageInfo.getTotal());//从pageInfo中获取总记录数并封装
        result.setRows(pageInfo.getList());//从pageInfo中获取分页数据并封装
        return result;
    }
    ```


Service层整合事务管理
  为了保证数据的一致性及原子性,Service的增删改操作应该添加事务。
  需求:添加一个新增两个用户的方法,测试事务是否添加成功。
在applicationContext-tx.xml中配置事务配置
  <!-- 配置事务管理器 -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"></property>
  </bean>

  <!-- 配置事务策略 -->
  <tx:advice id="txAdvice">
   <tx:attributes>
   <tx:method name="*"/>
   <tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
   </tx:attributes>
  </tx:advice>

  <!-- 配置aop切面 -->
  <aop:config>
   <!-- 配置切入点表达式 -->
   <aop:pointcut expression="execution(* cn.itcast.usermanage.service.impl.*.*(..))" id="pt1"/>
   <!-- 将通知应用到切入点 -->
   <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
  </aop:config>

导出Excel功能相关代码 
                    <!-- excel导出使用 -->
                    <dependency>
                                <groupId>org.apache.poi</groupId>
                                <artifactId>poi</artifactId>
                                <version>3.10.1</version>
                    </dependency>
                    <!-- 时间操作组件 -->
                    <dependency>
                                <groupId>joda-time</groupId>
                                <artifactId>joda-time</artifactId>
                    </dependency>

public class UserExcelView extends AbstractXlsView{
/**
* model:就是exportExcel方法中封装了数据model对象
* workbook:poi中的API,用来创建excel
*/
@Override
protected void buildExcelDocument(Map

    <!-- 注册自定义视图 -->
    <bean name="excelExportView" class="cn.itcast.usermanager.view.UserExcelView"></bean>

    <!--配置excel视图解析器 -->
    <bean  class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="1"></property>
    </bean>

MyBatis通用Mapper3
  通用Mapper可以极大的方便开发人员 可以随意的按照自己的需要选择通用方法 还可以很方便的开发自己的通用方法
  极其方便的使用MyBatis单标的增删改查
  支持单表操作 不支持通用的多表联合查询
  通用Mapper支持Mybatis-3.2.4及以上版本
  特别强调:
      不是表中字段的属性必须加上@Transient注解
      通用Mapper不支持devtools热加载 devtools排除实体类即可

                    <!-- 集成通用mapper -->
                    <dependency>
                                <groupId>com.github.abel533</groupId>
                                <artifactId>mapper</artifactId>
                                <version>2.3.4</version>
                    </dependency>

                    <!-- 通用mapper插件 -->
                    <plugin interceptor="com.github.abel533.mapperhelper.MapperInterceptor">
                                <!--主键自增回写方法,默认值MYSQL,详细说明请看文档 -->
                                <property name="IDENTITY" value="MYSQL" />
                                <!--通用Mapper接口-->
                                <property name="mappers" value="com.github.abel533.mapper.Mapper" />
                    </plugin>

                        /**
                         * 继承通用mapper接口
                         *
                         */

public interface NewUserMapper extends Mapper<User>{

                    }

@Table(name=”tb_user”)
public class User implements Serializable{
private static final long serialVersionUID = -4697379238361843294L;
@Id
@GeneratedValue(generator=”JDBC”)//一般都要配置主键,因为通用mapper中有通过主键查询的方法,如果不配置主键就无法执行通过主键查询的方法
@Column(name=”id”)//属性和字段一致可以不用配
private Long id;
// 用户名
private String userName;
// 密码
@JsonIgnore //当对象转化为json时候 忽略该属性
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
@DateTimeFormat(pattern=(“yyyy-MM-dd”))
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
@Transient //表里面没有对应字段需要配置
private String remark;
}

        @Before
        public void setUp() throws Exception {
                    //在测试时其实只要加载applicationContext.xml和applicationContext-mybatis.xml两个配置文件即可。
                    ApplicationContext ac = new ClassPathXmlApplicationContext("spring/applicationContext.xml","spring/applicationContext-mybatis.xml");
                    userMapper = ac.getBean(NewUserMapper.class);
        }

/**
* 通过条件进行查询:
* 查询年龄在15-30之间的并且用户名中有zhang的用户,或者密码是123456的用户。
* 显示的时候通过age倒序排序,如果age一样那么根据id正序执行。
*
* 1、初始化Example对象
* 2、通过Example对象获取Criteria来设置查询条件
* 3、可以通过example设置并集查询-or
* 4、可以通过example设置排序查询
*/
@Test
public void testSelectByExample() {
Example example = new Example(User.class);
Criteria criteria = example.createCriteria();
criteria.andBetween(“age”, 15, 30);
criteria.andLike(“userName”, “%”+”zhang”+”%”);
//添加or的条件
Criteria criteria2 = example.createCriteria();
criteria2.andEqualTo(“password”, “123456”);
example.or(criteria2);
//按年龄倒序排序,如果年龄相同,按id正序排序
example.setOrderByClause(“age desc,id asc”);
List list = userMapper.selectByExample(example);
for (User user : list) {
System.out.println(user);
}
}


RESTful Web Service 
  所谓的表述性状态转移:通俗的说就是原来的状态码的表述的意思发生了改变。(可参见http响应状态码)
  比如以前我们发送请求,只要请求执行成功,就会返回200状态码。但是现在,当比如提交表单,创建对象的操作时应该返回的是201状态码。
  再比如以前我们发送请求找不到页面报404,现在发送请求找不到资源(查不到数据)也应该报404等等。
        @GetMapping("{id}")
        @ResponseBody
        public ResponseEntity<User> testGet(@PathVariable("id")Long id){
                    try {
                                if(id==null||id<0){
                                            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
                                }
                                //int i=1/0;
                                User user = userService.queryUserById(id);
                                if(user==null){
                                            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
                                }
                                //return ResponseEntity.status(HttpStatus.OK).body(user);
                                return ResponseEntity.ok(user);
                    } catch (Exception e) {
                                e.printStackTrace();
                    }
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }

        /**如果不用返回数据 泛型就用void 并且调用build()方法
         * @param user
         * @return
         */
        @PostMapping
        public ResponseEntity<Void> testPost(User user){
                    try {
                                if(user==null||StringUtils.isBlank(user.getUserName())){
                                            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
                                }
                                boolean flag = userService.addUser(user);
                                if(flag){
                                            return ResponseEntity.status(HttpStatus.CREATED).build();
                                }
                    } catch (Exception e) {
                                e.printStackTrace();
                    }
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }

        /**相当于 @RequestMapping(method=RequestMethod.PUT)
         * @param user
         * @return
         */
        @PutMapping
        public ResponseEntity<Void> testPut(User user){
                    try {
                                if(user==null||user.getId()==null||user.getId()<0){
                                            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
                                }
                                boolean flag = userService.updateUser(user);
                                if(flag){
                                            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
                                }
                    } catch (Exception e) {
                                e.printStackTrace();
                    }
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }

        /**@RequestMapping(Method=RequestMethod.DELETE)
         * @param ids
         * @return
         */
        @DeleteMapping
        public ResponseEntity<Void> testDelete(@RequestParam("ids")Long []ids){
                    try {
                                if(ids==null||ids.length==0){
                                            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
                                }
                                boolean flag = userService.delUser(ids);
                                if(flag){
                                            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
                                }
                    } catch (Exception e) {
                                e.printStackTrace();
                    }
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }


  原因是:form表单默认只能以getpost请求提交数据,无法使用其他如put方法提交数据。
  解决方案:spring-web包中提供了一个叫做HttpPutFormContentFilter的过滤器,可以使得表单以put方法提交数据并封装到pojo中。
过滤器需要在web.xml中配置如下:
        <!-- 解决PUT请求无法提交表单数据的问题 -->
        <filter>
                    <filter-name>HttpMethodFilter</filter-name>
                    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
        </filter>
        <filter-mapping>
                    <filter-name>HttpMethodFilter</filter-name>
                    <url-pattern>/*</url-pattern>
        </filter-mapping>

请求无法提交delete方式的请求。
解决方案:使用DELETE相关的过滤器

    1. 在web.xml中配置delete请求的过滤

delete请求必须设置一个_method:DELETE,然后将一个post请求转化为DELETE请求或者PUT请求。


            <!--
                        将POST请求转化为DELETE或者是PUT
                        要用_method指定真正的请求参数
             -->
        <filter>
                    <filter-name>HiddenHttpMethodFilter</filter-name>
                    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        </filter>
        <filter-mapping>
                    <filter-name>HiddenHttpMethodFilter</filter-name>
                    <url-pattern>/*</url-pattern>
        </filter-mapping>

“`

  也可以设置为_method:PUT,然后将一个post请求转化为PUT请求。那么PUT请求的过滤器httpPutFormContentFilter也可以不用配置了,但是一般都会配置put请求的过滤器,这样比较方便,毕竟不需要设置_method属性

关于put和delete总结一句话:
 如果选择put方式 必须要配置HttpPutFormContentFilter 过滤器
 不能选择delete方式 必须选择post方式 同时必须配置HiddenHttpMethodFilter 同时post提交的时候增加_method:DELETE

对于put方式 也可以不配置HttpPutFormContentFilter 而选择配置HiddenHttpMethodFilter 同时post提交的时候增加_method:PUT

CREATED(201, “Created”)
NO_CONTENT(204, “No Content”)
MOVED_PERMANENTLY(301, “Moved Permanently”)
NOT_MODIFIED(304, “Not Modified”)
BAD_REQUEST(400, “Bad Request”)
NOT_FOUND(404, “Not Found”)
INTERNAL_SERVER_ERROR(500, “Internal Server Error”)

在后端使用rest风格时候对应前台的js代码稍许变化 例如使用rest删除的时候前台代码为

                    $.post("/user/rest",{'ids':ids,'_method':'DELETE'}, function(data,textStatus,xhr){
                                if(xhr.status == 204){
                                            $.messager.alert('提示','删除会员成功!',undefined,function(){
                                                        $("#userList").datagrid("reload");
                                            });
                                }
                    })

Maven加强
继承和聚合(多模块)
继承时需要注意:
  1.说到继承肯定是一个父子结构,子可以继承父。
  2.: 作为父模块的POM,其打包类型也必须为POM
  3.结构:父模块是为了帮助我们消除重复,版本统一和依赖复用。
  4.父子工程的版本建议要一致。

建立聚合工程需要注意:
  1.该聚合项目本身也做为一个Maven项目,它必须有自己的POM。
  2.它的打包方式必须为: pom
  3.引入了新的元素:module,子模块

  多模块解决的问题: 将maven项目分解,将一个大工程分成几个小的工程便于开发,为了统一管理和开发方便。
  继承解决的问题 : 使用pom配置,为了复用和整合,多个模块之间有关系,主要是指子模块和父工程之间的继承关系。例如:可以将公用的jar包放置到父工程
下,子工程继承就可以使用。
  提示:两者虽然概念不同,但在企业中会一起使用。

总结:
  对于聚合模块来说,它知道有哪些被聚合的模块。
  对于继承关系的子POM来说,它必须知道自己的父POM是谁。
在一些最佳实践中我们会发现:一个POM既是聚合POM,又是父POM,这么做主要是为了方便。

使用eclipse工具进行父子工程构建
  需求: usermanage工程使用maven来进行聚合和继承的重构。
抽取成以下:
  usermanage_parent:父工程(pom工程)
  usermanage_pojo:存放实体(jar工程)
  usermanage_mapper:存放接口(jar工程)
  usermanage_service:存放Service(jar工程)
  usermanage_controller:存放Action(war工程)

层之间的依赖设置
  层和层之间是相互调用的关系,由于这些都是不同的工程,因此要设置相互之间的依赖才能进行调用。

思考:怎么进行依赖?
  controller需要依赖service ,service需要依赖dao, Dao需要依赖entity,而由于传递性依赖的特性,entity也就能被其他层调用了。

注意:在执行依赖之前,先执行Maven install,将所有工程安装到本地,这样才能在添加依赖的时候通过坐标找到本地仓库中的依赖。

Maven生命周期
Maven有三套相互独立的生命周期,请注意这里说的是“三套”,而且“相互独立”,这三套生命周期分别是:
  Clean Lifecycle 在进行真正的构建之前进行一些清理工作。
  Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等。
  Site Lifecycle 生成项目报告,发布站点。
  再次强调一下它们是相互独立的,你可以仅仅调用clean来清理工作目录,仅仅调用site来生成站点。
当然你也可以直接运行 mvn clean install site 运行所有这三套生命周期。

  运行任何一个阶段的时候,它前面的所有阶段都会被运行,这也就是为什么我们运行mvn install 的时候,代码会被编译,测试,打包。此外,Maven的插件机制是完全依赖Maven的生命周期的,因此理解生命周期至关重要。

Maven插件
  Maven之所以可以运行,都是源于maven的插件。
  插件与生命周期绑定,用户可以通过执行生命周期命令来隐式的通过插件来执行任务。
  打包类型(packageing) 控制default生命周期和插件目标(plugin goal)的绑定。
  在maven整个生命周期的全过程,每个环节都是通过插件来完成的。

Lucene部分
  Lucene全文检索就是对文档中全部内容进行分词 然后对所有单词建立倒排索引的过程
  倒排索引:记录每个词条出现在哪些文档 以及文档中的位置 可以根据词条快速定位到包含这些词条的文档以及出现的位置

倒排索引创建索引的流程:
 1. 首先把所有的原始文档 进行编号,形成文档列表
 2. 把文档数据进行分词,得到很多的词条,以词条为索引。保存包含这些词条的文档的编号信息。

搜索的过程:
 1. 当用户输入任意的内容时,首先对用户输入的内容进行分词,得到用户要搜索的所有词条
 2. 然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。
 3. 然后根据这些编号去文档列表中找到文档

字段使用方式小结

问题1:如何确定一个字段是否需要存储?
 如果一个字段要显示到最终的结果中,那么一定要存储,否则就不存储

问题2:如何确定一个字段是否需要创建索引?
 如果要根据这个字段进行搜索,那么这个字段就必须创建索引。

问题3:如何确定一个字段是否需要分词?
 前提是这个字段首先要创建索引。然后如果这个字段的值是不可分割的,那么就不需要分词。例如:ID,产品编号

1)DoubleField、FloatField、IntField、LongField、StringField、TextField这些子类一定会被创建索引。但是不一定会被存储到文档列表。要通过构造函数中的参数 
Store来指定:如果Store.YES代表存储,Store.NO代表不存储
如果数据要展示给用户一定要保存
2)TextField即创建索引,又会被分词。StringField会创建索引,但是不会被分词。
  如果内容不分词,会造成整个字段内容作为一个词条,除非用户完全匹配,否则搜索不到:
3)StoreField一定会被存储,但是 一定不 创建索引

solrCloud部分:

单点存在的问题:
 A:并发处理能力有限。
 B:容错率低,一旦服务器故障,整个服务就无法访问了。
 C:单台服务器计算能力低,无法完成复杂的海量数据计算。

集群的特点
  集群是指将多台服务器集中在一起,每台服务器都,实现相同的业务,做相同的事情。但是每台服务器并不是缺一不可,存在的作用主要是缓解并发压力和单点故障转移问题。可以利用一些廉价的符合工业标准的硬件构造高性能的系统。实现:高扩展、高性能、低成本、高可用!

伸缩性(Scalability)
高可用性(High availability) 
负载均衡(Load balancing)
协同调配,相对均等,容错处理
高性能 (High Performance ) 时间越短性能越好

传统架构的特点:
A:系统过于庞大,开发维护困难
B:功能间耦合度太高:
C:无法针对单个模块进行优化,
D:无法进行水平扩展

分布式架构的特点:
  每个Web服务器(Tomcat)程序都负责一个网站中不同的功能,缺一不可。如果某台服务器故障,则对应的网站功能缺失,也可以导致其依赖功能甚至全部功能都不能够使用。因此,分布式系统需要运行在集群服务器中,甚至分布式系统的每个不同子任务都可以部署集群
  分布式是指将多台服务器集中在一起,每台服务器都实现总体中的不同业务,做不同的事情。并且每台服务器都缺一不可,如果某台服务器故障,则网站部分功能缺失,或导致整体无法运行。存在的主要作用是大幅度的提高效率,缓解服务器的访问和存储压力。

分布式集群架构
  一般分布式中的每一个节点,都可以做集群。这样的系统架构,我们通常称为分布式集群架构。
  集群:所有的机器处理的业务都一样。
  分布式:所有模块都互不相同。

正向代理
  一般情况下,如果没有特别说明,代理技术默认说的是正向代理技术。关于正向代理的概念如下: 正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器Z向服务器B转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

简单来说,正向代理就是代理服务器替代访问方【用户A】去访问目标服务器【服务器B】

为什么需要使用正向代理?
  1、 访问本无法访问的服务器B
   great firewall
   被服务器屏蔽(wow)
  2、 加速访问服务器B
  3、Cache作用
如果在用户A访问服务器B某数据J之前,已经有人通过代理服务器Z访问过服务器B上得数据J,那么代理服务器Z会把数据J保存一段时间,如果有人正好取该数据J,那么代理服务器Z不再访问服务器B,而把缓存的数据J直接发给用户A。
  4、客户端访问授权
  5、隐藏访问者的行踪
服务器B并不知道访问自己的实际是用户A,因为代理服务器Z代替用户A去直接与服务器B进行交互。如果代理服务器Z被用户A完全控制(或不完全控制),会惯以“肉鸡”术语称呼。

正向代理:
1. 在客户机配置代理服务器,
2. 确认代理服务器的位置不和client在过滤器的同侧
如果没有特殊说明,所有的代理都称为正向代理。

反向代理(reverse proxy)
反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端。 使用反向代理服务器的作用如下:
  1.保护和隐藏原始资源服务器
  用户A始终认为它访问的是原始服务器B而不是代理服务器Z,但实用际上反向代理服务器接受用户A的应答,从原始资源服务器B中取得用户A的需求资源,然后发送给用户A。由于防火墙的作用,只允许代理服务器Z访问原始资源服务器B。在这个虚拟的环境下,防火墙和反向代理的共同作用保护了原始资源服务器B,但用户A并不知情。

  2.负载均衡
  当反向代理服务器不止一个的时候,我们甚至可以把它们做成集群,当更多的用户访问资源服务器B的时候,让不同的代理服务器Z(x)去应答不同的用户,然后发送不同用户需要的资源。而且反向代理服务器像正向代理服务器一样拥有CACHE的作用,它可以缓存原始资源服务器B的资源,而不是每次都要向原始资源服务器B请求数据,特别是一些静态的数据,比如图片和文件。
简单来说,反向代理就是反向代理服务器替代原始服务器【服务器B】让【用户A】去访问
实现反向代理:
1. 用户:不需要任何配置,该怎么访问怎么访问
2. 服务器,需要在服务器配置服务和代理服务器之间的关系

solr的分布式解决方案
 1,数据量太大,一台机器放不下,一台机器的性能不行
 2,如何分?分原始的collection(核心),分成一个个shard,只要shard>1就是分布式
 3,由于存在单点故障,所以要对分片shard做集群操作。只要副本数量>1就是 集群(多个副本不在同一台机器)
 4,Collection是逻辑的,由多个shard组成,
 5, Replica副本,可以有多个,只要副本>1并且没有存储在同一台机器就是集群

SolrCloud逻辑结构详解

为了实现海量数据的存储,我们会把索引进行分片(Shard),把分片后的数据存储到不同Solr节点。

为了保证节点数据的高可用,避免单点故障,我们又会对每一个Shard进行复制,产生很多副本(Replicas),每一个副本对于一个Solr节点中的一个core

用户访问的时候,可以访问任何一个会被自动分配到任何一个可用副本进行查询。

Collection:在SolrCloud集群中逻辑意义上的完整的索引。一般会包含多个Shard(分片),如果大于1个分片,那么就是分布式存储。
Shard: Collection的逻辑分片。每个Shard被化成一个或者多个replicas(副本)
Replica: Shard的一个副本,存储在Solr集群的某一台机器中(就是一个节点),对应这台Solr的一个Core,如果机器上存放了多个副本,那本机器的solr将有多个core。
Collection和Shard只是逻辑存在的 ,真实存在的只有replica,replica其实就是shard。

Zookeeper分布式协调服务
Zookeeper是集群分布式中大管家
分布式集群系统比较复杂,子模块很多,但是子模块往往不是孤立存在的,它们彼此之间需要协作和交互,各个子系统就好比动物园里的动物,为了使各个子系统能正常为用户提供统一的服务,必须需要一种机制来进行协调 —— 这就是ZooKeeper

Zookeeper 是为分布式应用程序提供高性能协调服务的工具集合,也是Google的Chubby一个开源的实现,是Hadoop 的分布式协调服务。

在ZooKeeper集群当中,集群中的服务器角色有两种:1个Leader和多个Follower,具体功能如下:
1)领导者(leader),负责进行投票的发起和决议,监控集群中的节点是否存活(心跳机制),进行分配资源
2)follower用于接受客户端请求并向客户端返回结果,在选主过程中参与投票
特点:
A:Zookeeper:一个leader,多个follower组成的集群
B:全局数据一致(leader主持):每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
C:数据更新原子性,一次数据更新要么成功。
D:实时性,在一定时间范围内,client能读到最新数据,
E:半数机制:整个集群中只要有一半以上存活,就可以提供服务。因此通常Zookeeper由2n+1(n>=0)台servers组成,每个server都知道彼此的存在。每个server都维护的内存状态镜像以及持久化存储的事务日志和快照。为了保证Leader选举能获得到多数的支持,所以ZooKeeper集群的数量一般为奇数。对于2n+1台server,只要有n+1台(大多数)server可用,整个系统保持可用
Zookeeper的作用
Zookeeper包含一个简单的 原语集 ,分布式应用程序可以基于它实现:命名服务、配置维护、集群选主等
命名服务:注册节点信息,形成有层次的目录结构(类似Java的包名)。
配置维护:配置信息的统一管理和动态切换(solrconfig.xml,schame.xml)
集群选主:确保整个集群中只有一个主,其它为从。并且当主挂了后,可以自动选主(同一shard的多个副本之间选主)

solrCloud单机部署
jdk安装:
tar -zxvf jdk-7u71-linux-x64.tar.gz
cd jdk1.7.0_71/
vim /etc/profile
加入 export JAVA_HOME=/usr/local/myapp/jdk1.7.0_71
export PATH= P A T H : JAVA_HOME/bin
source /etc/profile
echo J A V A H O M E e c h o PATH
apache安装配置
tar -zxvf apache-xxx
pwd:/usr/local/myapp/apache-tomcat-7.0.57
vim /etc/profile
加入 export CATALINA_HOME=/usr/local/myapp/apache-tomcat-7.0.57
source /etc/profile
service iptables off 单次关闭防火墙
chkconfig iptables off 关闭防火墙开机自启动
solr安装 配置
tar -zxvf solr-xxxx
cd solr/example/webapps
cp solr.war /usr/local/myapp/apache-tomcat-7.0.57/webapps/ 复制solr.war到tomcat的webapps下
unzip -oq solr.war -d solr
删除war包
rm -rf solr.war
cd /usr/local/myapp/apache-tomcat-7.0.57/webapps/solr/WEB-INF/lib
从本地导入solr在tomcat运行需要导入的jar包\lib
修改tomcat的bin目录下的catalina.sh文件,添加启动的参数,指向solr的索引文件夹
export JAVA_OPTS=-Dsolr.solr.home=/usr/local/myapp/solr-4.10.2/example/solr
zookeeper安装配置
tar -zxvf zookeeper-3.4.5.tar.gz
mv zookeeper-3.4.5 zookeeper
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
要添加的内容:
dataDir=/usr/local/myapp/zookeeper/data
dataLogDir=/usr/local/myapp/zookeeper/log
server.1=192.168.206.101:2888:3888
server.2=192.168.206.102:2888:3888
server.3=192.168.206.103:2888:3888

    mkdir -m 755 data
    mkdir -m 755 log
    cd data
    vi myid
    插入内容:1

    vi /etc/profile(修改文件)
    添加内容:
    export ZOOKEEPER_HOME=/usr/local/myapp/zookeeper
    export PATH=$PATH:$ZOOKEEPER_HOME/bin
    重新编译文件:

    source /etc/profile
启动zookeeper: zkServer.sh start
       停止zookeeper: zkServer.sh stop
       查看状态: zkServer.sh status

taotao的项目结构要理顺一下

猜你喜欢

转载自blog.csdn.net/huming1250446609/article/details/82432525