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,复杂类型通过{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>
springmvc源码执行流程
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的测试
<!-- 开启注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 处理静态资源被拦截的问题 -->
<mvc:default-servlet-handler/>
<!-- 开启包扫描 -->
<context:component-scan base-package="cn.itcast.usermanage.controller"></context:component-scan>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean
测试正常 需要完成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<String, Object> model,
Workbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
List<User> userList= (List<User>) model.get("userList");
Sheet sheet = workbook.createSheet("会员列表");
Row header = sheet.createRow(0);
header.createCell(0).setCellValue("ID");
header.createCell(1).setCellValue("用户名");
header.createCell(2).setCellValue("姓名");
header.createCell(3).setCellValue("年龄");
header.createCell(4).setCellValue("性别");
header.createCell(5).setCellValue("出生日期");
header.createCell(6).setCellValue("创建日期");
header.createCell(7).setCellValue("更新日期");
int rowNum=1;
for (User user : userList) {
Row row = sheet.createRow(rowNum);
row.createCell(0).setCellValue(user.getId());
row.createCell(1).setCellValue(user.getUserName());
row.createCell(2).setCellValue(user.getName());
row.createCell(3).setCellValue(user.getAge());
String sex;//从对象中获取的性别是1和0,需要在表中显示男和女
if (user.getSex()==1) {
sex = "男";
}else if (user.getSex()==2) {
sex = "女";
}else {
sex = "未知";
}
row.createCell(4).setCellValue(sex);
row.createCell(5).setCellValue(new DateTime(user.getBirthday()).toString("yyyy-MM-dd"));
row.createCell(6).setCellValue(new DateTime(user.getCreated()).toString("yyyy-MM-dd hh:mm:ss"));
row.createCell(7).setCellValue(new DateTime(user.getUpdated()).toString("yyyy-MM-dd"));
rowNum++;
}
// 设置相应头信息,以附件形式下载并且指定文件名
response.setHeader("Content-Disposition", "attachment;filename=" + new String("会员列表.xls".getBytes(),"ISO-8859-1"));
}
}
<!-- 注册自定义视图 -->
<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>
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<User> 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表单默认只能以get和post请求提交数据,无法使用其他如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
:
PATH:
PATH:JAVA_HOME/bin
source /etc/profile
echo $JAVA_HOME
echo $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=
P
A
T
H
:
PATH:
PATH:ZOOKEEPER_HOME/bin
重新编译文件:
source /etc/profile
启动zookeeper: zkServer.sh start
停止zookeeper: zkServer.sh stop
查看状态: zkServer.sh status
taotao的项目结构要理顺一下