**SSM(Spring+SpringMVC+MyBatis)框架集由Spring、SpringMVC、MyBatis三个开源框架整合而成,常作为数据源较简单的web项目的框架。
其中spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
SpringMVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring,springmvc仅给spring的表现层提供支持。 **
一. 整合 MyBatis
创建项目:ssm-xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>ssm-xml</artifactId>
<packaging>war</packaging>
<dependencies>
//springframework的依赖 webmvc tx jdbc aop aspect
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
//javax.servle依赖 javax.servle-api jsp-api
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
//mysql连接 没有sql会报错
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
//myabatis依赖 原生 和 mybatis—spring整合
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
<!--引入分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
//json依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10</version>
</dependency>
<!--引入hikaricp数据源连接池-->
<dependency>
<groupId>dev.tuxjsql</groupId>
<artifactId>hikaricp-cp</artifactId>
</dependency>
<!--引入上传文件解析器,前页面需要上传东西的时候会用到-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- tomcat7插件,命令: mvn tomcat7:run -DskipTests -->
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<uriEncoding>UTF-8</uriEncoding>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Account.java
package com.itheima.account.pojo;
public class Account {
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
AccountMapper.java
package com.itheima.account.mapper;
import com.itheima.account.pojo.Account;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface AccountMapper {
@Select("select id, name, money from account")
List<Account> findAll();
}
AccountService.java
package com.itheima.account.service;
import com.itheima.account.pojo.Account;
import java.util.List;
public interface AccountService {
List<Account> findAll();
}
AccountServiceImpl.java
package com.itheima.account.service.impl;
import com.itheima.account.mapper.AccountMapper;
import com.itheima.account.pojo.Account;
import com.itheima.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public List<Account> findAll() {
return this.accountMapper.findAll();
}
}
applicatonContext.xml
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1. 加载数据库连接信息 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2. dataSource 配置是为了下面的 SqlSessionFactoryBean 会依赖于它 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 3. SqlSessionFactoryBean 负责创建 sqlSessionFactory 对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 3.1 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 4. MapperScannerConfigurer 负责扫描 mybatis mapper 接口 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.account.mapper"/>
</bean>
<!-- 5. 扫描所有 service -->
<context:component-scan base-package="com.itheima.account.service"/>
<!--6.定义事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--7.开启@Transactional注解扫描-->
<tx:annotation-driven/>
</beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb03
jdbc.username=root
jdbc.password=root
1. 初步整合
- 配置数据源 - 代码片段1
- 将 sqlSessionFactory 交给 Spring 管理 - 代码片段2
- 扫描 mapper 接口,交给 Spring 管理 - 代码片段3
代码片段1
<!-- 1. 加载数据库连接信息 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2. dataSource 配置是为了下面的 SqlSessionFactoryBean 会依赖于它 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
代码片段2
<!-- 3. SqlSessionFactoryBean 负责创建 sqlSessionFactory 对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 3.1 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
代码片段3
<!-- 4. MapperScannerConfigurer 负责扫描 mybatis mapper 接口 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.account.mapper"/>
</bean>
当然,还需要扫描所有 service
<!-- 5. 扫描所有 service -->
<context:component-scan base-package="com.itheima.account.service"/>
2. 进阶整合
但现实总是没那么简单,例如我们在使用 MyBatis 时需要做一些
- settings 通用配置
- typeAlias 别名配置
- plugins 插件配置
- xml mapper 文件不在标准位置
方式1 直接集成 mybatis-config.xml
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.account.mapper.AccountMapper">
<select id="findAll" resultType="account">
select id, name, money from account
</select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.itheima.account.pojo"/>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb03"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/AccountMapper.xml"/>
</mappers>
</configuration>
注意
- mybatis-config.xml 中即使配置了数据源信息,最后也会被 Spring 的配置覆盖掉,因此可以省略掉!
与之前相比,只需要修改配置中 SqlSessionFactoryBean 部分
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 3.1 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 3.2 引用 mybatis-config.xml 文件中的所有配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
方式2 干掉 mybatis-config.xml
与之前相比,需要较多修改配置中 SqlSessionFactoryBean 部分,其实就是把配置一点点转移至 Spring 配置文件
<!-- 3. SqlSessionFactoryBean 负责创建 sqlSessionFactory 对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 3.1 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 3.2 配置驼峰命名 -->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
<!-- 3.3 配置别名 -->
<property name="typeAliasesPackage" value="com.itheima.account.pojo"/>
<!-- 3.4 配置插件-->
<property name="plugins">
<array>
<!-- 分页插件-->
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
<!-- 3.5 配置 xml mapper 所在位置 -->
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>
注意
- 下划线驼峰命名转换,仅对于查询封装实体对象有效,增删改无效
- 如果只用接口 mapper,则用不上别名,3.3 和 3.5 的配置都可以省略
- 如果 xml mapper 文件在标准位置,则 3.5 的配置可以省略
二. 整合 Spring MVC
1. 父子容器方式整合 SpringMVC
AccountController.java
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@GetMapping("/listAccounts")
@ResponseBody
public List<Account> listAll(){
return this.accountService.findAll();
}
}
父容器(Root SpringApplicationContext)
src/main/resources/applicationContext.xml
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1. 加载数据库连接信息 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2. dataSource 配置是为了下面的 SqlSessionFactoryBean 会依赖于它 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 3. SqlSessionFactoryBean 负责创建 sqlSessionFactory 对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 3.1 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 3.2 配置驼峰命名 -->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
<!-- 3.3 配置别名 -->
<property name="typeAliasesPackage" value="com.itheima.account.pojo"/>
<!-- 3.4 配置插件-->
<property name="plugins">
<array>
<!-- 分页插件-->
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
<!-- 3.5 配置 xml mapper 所在位置 -->
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>
<!-- 4. MapperScannerConfigurer 负责扫描 mybatis mapper 接口 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.account.mapper"/>
</bean>
<!-- 5. 扫描所有 service -->
<context:component-scan base-package="com.itheima.account.service"/>
<!--6.定义事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--7.开启@Transactional注解扫描-->
<tx:annotation-driven/>
</beans>
注意:它的配置方式就是综合了整合 MyBatis 和整合事务的部分,关键在于理解每项配置能做、要做的事情,而不是去死记硬背!
子容器(Servlet MvcApplicationContext)
src/main/resources/springmvc.xml
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--1,扫描controller-->
<context:component-scan base-package="com.itheima.account.controller"/>
<!--2,注册扩展bean-->
<mvc:annotation-driven/>
<!--3,配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--4,配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 4.1,设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 4.2,设定文件上传的最大值5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
</bean>
</beans>
web.xml
在 web.xml 中初始化这两个容器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--初始化父容器部分-->
<!--1,配置ContextLoaderListener监听器,Web应用程序启动时载入Ioc容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--2,配置全局context的文件目录-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<!--初始化子容器部分-->
<!--3,配置DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--3.1,定位springmvc.xml的位置,移动到classpath目录方便管理-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
//加载顺序,越小越先加载
<load-on-startup>1</load-on-startup>
</servlet>
<!--4,配置dispatcherServlet的拦截路径,除jsp之外的其他所有路径-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--5,配置编码过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--5.1,设置编码类型为utf-8-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<!--5.2,配置拦截路径,为所有路径-->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
小结:controller,service,以及mapper三层分别扫描,自己扫描自己,互不干扰,防止出现重复扫描
三,基于整合后的增删改查
AccountController.java
package com.itheima.account.controller;
import com.itheima.account.pojo.Account;
import com.itheima.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
/**
* 列出所有的账户信息
*
* @return
*/
@GetMapping
@ResponseBody
public List<Account> listAll() {
return this.accountService.findAll();
}
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Account queryAccountById(@PathVariable("id") Long id) {
return this.accountService.queryAccountById(id);
}
/**
* 添加账户
*
* @param account
* @return
*/
@PostMapping(produces = "application/json;charset=utf-8;")
@ResponseBody
public String addAccount(@RequestBody Account account) {
this.accountService.addAccount(account);
return "账户添加成功";
}
/**
* 修改账户
*
* @param account
* @return
*/
@PutMapping(value = "/{id}", produces = "application/json;charset=utf-8;")
@ResponseBody
public String updateAccount(@PathVariable("id") Integer id, @RequestBody Account account) {
this.accountService.updateAccount(id,account);
return "账户修改成功";
}
/**
* 指定id删除账户
* @param id
* @return
*/
@DeleteMapping(value = "/{id}",produces = "application/json;charset=utf-8;")
@ResponseBody
public String deleteAccount(@PathVariable("id")Integer id){
this.accountService.deleteAccount(id);
return "账户删除成功";
}
}
AccountService.java
package com.itheima.account.service;
import com.itheima.account.pojo.Account;
import java.util.List;
public interface AccountService {
List<Account> findAll();
Account queryAccountById(Long id);
void addAccount(Account account);
void updateAccount(Integer id, Account account);
void deleteAccount(Integer id);
}
AccountServiceImpl.java
package com.itheima.account.service.impl;
import com.itheima.account.mapper.AccountMapper;
import com.itheima.account.pojo.Account;
import com.itheima.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public List<Account> findAll() {
return this.accountMapper.findAll();
}
@Override
public Account queryAccountById(Long id) {
return this.accountMapper.queryAccountById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addAccount(Account account) {
this.accountMapper.addAccount(account);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAccount(Integer id, Account account) {
this.accountMapper.updateAccount(id, account);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAccount(Integer id) {
this.accountMapper.deleteAccount(id);
}
}
AccountMapper.java
package com.itheima.account.mapper;
import com.itheima.account.pojo.Account;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface AccountMapper {
//@Select("select id, name, money from account")
List<Account> findAll();
Account queryAccountById(Long id);
void addAccount(Account account);
void updateAccount(@Param("id") Integer id, @Param("account") Account account);
void deleteAccount(@Param("id") Integer id);
}
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.account.mapper.AccountMapper">
<insert id="addAccount">
insert into account (id, name, money)values (#{id},#{name},#{money})
</insert>
<update id="updateAccount">
update account set name = #{account.name} ,money = #{account.money} where id = #{id}
</update>
<delete id="deleteAccount">
delete from account where id = #{id}
</delete>
<select id="findAll" resultType="account">
select id, name, money
from account
</select>
<select id="queryAccountById" resultType="account">
select id, name, money
from account
where id = #{id}
</select>
</mapper>