一、新建項目,並添加所須要的jar包:javascript
分析:html
(1)在springboot中已經繼承了Redis,就是說一旦咱們啓動項目,它是能夠根據properties配置文件中的配置信息自動建立Jedis對象的java
(2)咱們想要的是本身定義配置信息的名稱,再根據本身定義的配置信息配置一個有關配置信息的類,最後根據這個類來建立一個咱們想要的Jedis對象。node
(3)springboot項目在加載的時候,因爲springboot沒法從配置文件中獲取想要的信息,因此不會自動建立屬於springboot的Jedis對象,而是根據咱們自定義mysql
的配置信息進行建立一個Jedis對象jquery
(4)在使用Jedis對象的時候,咱們給咱們的Jedis對象加上了@Bean註解,在要使用Jedis的類中使用@Autowired註解,因此spring中的BeanFactory工廠自動建立對象,web
,且是單例的;此處直接在@Autowire的註解下注入的是(3)中的對象,也就是根據咱們自定義信息建立的對象ajax
springboot-parent
spring-boot-web-start
mysql驅動包
druid(德魯伊)鏈接池
springboot和mybatis的整合包
thymeleaf模板jar包
thymeleaf忽略語法的jar包
springboot和redis的整合包
jedis的包:
jedis就是經過Java實現對redis集羣的增刪改查,就是操做redis數據庫
redis最終只支持5種數據格式:
String類型是最經常使用的
若是須要把User對象存入到redis中===>須要把User對象轉換爲json字符串===>再把字符串存入到redis中
json的jar包
configuration-processer包(爲了使用其中的@ConfigurationProperties()註解,在下文中的RedisProperties類中所使用)
1 <!-- springboot的jar --> 2 <parent> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-parent</artifactId> 5 <version>1.5.10.RELEASE</version> 6 </parent> 7 8 <dependencies> 9 <!-- 10 springboot-starter-web 11 --> 12 <dependency> 13 <groupId>org.springframework.boot</groupId> 14 <artifactId>spring-boot-starter-web</artifactId> 15 </dependency> 16 <!-- 17 springboot-mybatis整合包 18 --> 19 <dependency> 20 <groupId>org.mybatis.spring.boot</groupId> 21 <artifactId>mybatis-spring-boot-starter</artifactId> 22 <version>1.3.0</version> 23 </dependency> 24 <!-- 25 mysql的驅動包 26 --> 27 <dependency> 28 <groupId>mysql</groupId> 29 <artifactId>mysql-connector-java</artifactId> 30 <version>5.1.38</version> 31 </dependency> 32 <!-- 33 druid鏈接池 34 --> 35 <dependency> 36 <groupId>com.alibaba</groupId> 37 <artifactId>druid</artifactId> 38 <version>1.1.10</version> 39 </dependency> 40 <!-- 41 html的thymeleaf模板 42 --> 43 <dependency> 44 <groupId>org.springframework.boot</groupId> 45 <artifactId>spring-boot-starter-thymeleaf</artifactId> 46 </dependency> 47 <!-- 48 redis的jar包以及jedis的jar包 49 --> 50 <dependency> 51 <groupId>redis.clients</groupId> 52 <artifactId>jedis</artifactId> 53 <version>2.9.0</version> 54 </dependency> 55 <!-- 56 redis和springboot的整合包 57 --> 58 <dependency> 59 <groupId>org.springframework.data</groupId> 60 <artifactId>spring-data-redis</artifactId> 61 </dependency> 62 <!-- 63 fastjson包 64 --> 65 <dependency> 66 <groupId>com.fasterxml.jackson.core</groupId> 67 <artifactId>jackson-databind</artifactId> 68 <version>2.8.1</version> 69 </dependency> 70 <!-- 71 添加springboot的進程jar包 72 裏面包含了properties文件的讀取(其實就是包含了@ConfigurationProperties()註解) 73 --> 74 <dependency> 75 <groupId>org.springframework.boot</groupId> 76 <artifactId>spring-boot-configuration-processor</artifactId> 77 <optional>true</optional> 78 </dependency> 79 80 <dependency> 81 <groupId>net.sourceforge.nekohtml</groupId> 82 <artifactId>nekohtml</artifactId> 83 <version>1.9.21</version> 84 </dependency> 85 </dependencies>
二、在springboot框架中,已經整合了Redis,能夠直接使用。
此處根據springboot整合Redis的源碼來實現本身的Redis,緣由:springboot自帶的Redis已經很是完善,一旦出錯,很難找到錯誤的地方
例如:當服務器的IP變更,端口號的問題等,能夠經過在本身的配置類中打斷點等方法判斷錯誤的地方
三、在config中配置application.properties配置信息:
1 server.port=8081 2 server.context-path=/ 3 4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 5 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?userSSL=false 6 spring.datasource.username=root 7 spring.datasource.password=123456 8 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 9 10 mybatis.type-aliases-package=com.aaa.liu.redis.model 11 mybatis.mapper-locations=classpath:mapper/*Mapper.xml 12 13 #開始自定義配置 14 # 配置redis集羣的端口號和IP 15 spring.redis.nodes=192.168.134.130:6380,192.168.134.130:6381,192.168.134.130:6382,192.168.134.130:6383,192.168.134.130:6384,192.168.134.130:6385 16 # 配置了redistribution的最大鏈接數 17 spring.redis.maxAttempts=1000 18 # 配置了redis最大超時時間 19 spring.redis.commandTimeout=500000 20 # 配置了redis的失效時間 21 spring.redis.expire=1000 22 23 # 定義圖書的key值 24 book_key=bookKey1 25 26 # 配置thymeleaf模板(不配置也能夠,直接使用) 27 # 配置thymeleaf緩存:默認值true,須要手動修改成false 28 spring.thymeleaf.cache=false 29 # 配置不嚴謹的html 30 spring.thymeleaf.mode=LEGACYHTML5
四、在Java中新建一個config包,在包中新建RedisPro類,將剛剛application.properties的配置信息引入此類中:
1 package com.aaa.liu.redis.config; 2 3 import org.springframework.boot.context.properties.ConfigurationProperties; 4 import org.springframework.stereotype.Component; 5 6 /** 7 * @Author 劉其佳 8 * @DateTime 2019/8/28 19:49 9 * @Project_Name SpringBootRedis 10 * 11 * 一、@Component:把RedisProperties做爲spring的一個組件 12 * 做用是 把Redis所須要配置和鏈接信息封裝進RedisProperties類中 13 * 該組件可拆分 14 * 配置該組件類的做用是從application.properties文件中讀取自定義的信息 15 * 二、@ConfigurationPrperties:做用是把application.properties中的屬性讀進RedisProperties類中 16 * 當使用該註解的時候必需要使用spring-boot-configuration-processor的jar包 17 * prefx="spring.redis";做用是選中在application.properties文件中的 屬性名 進行對用(必須一致) 18 * 三、原本按理說在此類中定義屬性事後,只有get方法,不該該有set方法,由於不能別其餘類修改 19 * 但如果真的不放入set方法的話,在使用此類時,沒法傳遞進來參數(屬性是私有的) 20 * !!!因此最終仍是要有set方法,只要注意其餘的類在調用RedisProperties類中的屬性的時候, 21 * 必定不能修改!!! 22 * 23 */ 24 25 @Component 26 /** 27 * 全部以spring.redis開頭的配置信息 28 */ 29 @ConfigurationProperties(prefix = "spring.redis") 30 public class RedisProperties { 31 32 /** 33 * 下列屬性對應application.properties文件中的自定義的配置: 34 * redis集羣的節點 35 * 最大鏈接數 36 * 最大超時時間 37 * 失效時間 38 */ 39 private String nodes; 40 private String maxAttempts; 41 private String commandTimeout; 42 private String expire; 43 44 public String getNodes() { 45 return nodes; 46 } 47 48 public void setNodes(String nodes) { 49 this.nodes = nodes; 50 } 51 52 public String getMaxAttempts() { 53 return maxAttempts; 54 } 55 56 public void setMaxAttempts(String maxAttempts) { 57 this.maxAttempts = maxAttempts; 58 } 59 60 public String getCommandTimeout() { 61 return commandTimeout; 62 } 63 64 public void setCommandTimeout(String commandTimeout) { 65 this.commandTimeout = commandTimeout; 66 } 67 68 public String getExpire() { 69 return expire; 70 } 71 72 public void setExpire(String expire) { 73 this.expire = expire; 74 } 75 }
五、仍然在config包中新建類:RedisConfiguration,將RedisProperties類注入進來:
1 package com.aaa.liu.redis.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import redis.clients.jedis.HostAndPort; 9 import redis.clients.jedis.JedisCluster; 10 11 import java.util.HashSet; 12 import java.util.Set; 13 14 /** 15 * @Author 劉其佳 16 * @DateTime 2019/8/28 20:34 17 * @Project_Name SpringBootRedis 18 */ 19 20 /** 21 * 此處加上註解@Configuration或者@SpringBootApplication 22 * 23 * 由於在SpringBootApplication是一個組合註解 24 * 進入其中能夠看到@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 25 * 再次進入@SpringBootConfiguration能夠看到@Configuration 26 * 所以SpringBootConfiguration是EnableAutoConfiguration、ComponentScan和Configuration的組合註解 27 * 28 * 在較低版本的SpringBoot中: 29 * @Configuration是啓用基於Java的配置 30 * @ComponentScan是啓用組件掃描。 31 * @EnableAutoConfiguration是啓用SpringBoot的自動配置功能 32 * 引用自:https://blog.csdn.net/qq_37939251/article/details/83058065 33 */ 34 35 /** 36 * 經過SpringBootApplication/Configuration註解將RedisConfiguration類標識爲springboot的配置類 37 * 38 * 使用Jedis的jar包操做Redis集羣數據庫 39 * 一、Redis:標識了Redis是單節點模式(只有一臺Redis) 40 * 源碼中:protected static class RedisConfiguration {} 41 * 二、RedisCluster:標識了Redis的Redis集羣模式 42 * 源碼中:protected final RedisClusterConfiguration getClusterConfiguration() {} 43 * 本次只配置集羣版的Redis 44 * 45 * @Bean: 至關於applicationContext.xml中的<bean id=""> class="JedisCluster.class"</bean> 46 * 47 * @EnableConfirgurationProperties({RedisProperties.class})是源碼中使用的註解 48 * 在本類中咱們使用@Autowired來代替掉 49 */ 50 @SpringBootApplication 51 public class RedisConfiguration { 52 @Autowired 53 private RedisProperties redisProperties; 54 55 @Bean 56 public JedisCluster getJedisCluster(){ 57 //一、獲取到application.properties文件中的Redis集羣的節點信息 58 String nodes=redisProperties.getNodes(); 59 //二、使用split來將多個node進行拆分 60 String[] nodeArray = nodes.split(","); 61 //三、建立一個set集合,以HostAndPort對象做爲泛型 62 Set<HostAndPort> hostAndPortSet=new HashSet<HostAndPort>(); 63 //四、遍歷剛剛獲得的節點數組 64 for (String node : nodeArray) { 65 //五、繼續使用split將node拆分紅IP和端口號(host、port) 66 String[] hostAndPortArray = node.split(":"); 67 //六、建立HostAndPort對象,並將獲得的IP和端口號做爲構造方法的參數傳入 68 HostAndPort hostAndPort=new HostAndPort(hostAndPortArray[0],Integer.parseInt(hostAndPortArray[1])); 69 //七、把每個HostAndPort對象裝進Set集合中 70 hostAndPortSet.add(hostAndPort); 71 } 72 //八、返回一個JedisCluster對象 73 return new JedisCluster(hostAndPortSet,Integer.parseInt(redisProperties.getCommandTimeout()),Integer.parseInt(redisProperties.getMaxAttempts())); 74 } 75 }
六、在Service層中新建RedisService
1 package com.aaa.liu.redis.service; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import redis.clients.jedis.JedisCluster; 6 7 /** 8 * @Author 劉其佳 9 * @DateTime 2019/8/28 21:39 10 * @Project_Name SpringBootRedis 11 * 在之前的UserService中注入UserMapper,對數據庫進行操做 12 * 如今的RedisService中注入JedisCluster,對Redis數據庫進行操做 13 */ 14 @Service 15 public class RedisService { 16 17 @Autowired 18 private JedisCluster jedisCluster; 19 20 /** 21 * 在Redis數據庫中的增刪查(沒有修改)分別爲: 22 * set del get 23 * 在下面分別定義 24 */ 25 26 /** 27 * @author 劉其佳 28 * @description 29 * 向Redis數據庫中存入String類型的數據 30 * set的時候返回值是"ok" 31 * @param * param *:key 32 * @date 2019/8/28 33 * @return java.lang.String 34 * @throws 35 */ 36 public String set(String key,String value){ 37 return jedisCluster.set(key,value); 38 } 39 40 /** 41 * @author 劉其佳 42 * @description 43 * 從Redis數據庫中經過key取出數據 44 * @param * param *:key 45 * @date 2019/8/28 46 * @return java.lang.String 47 * @throws 48 */ 49 public String get(String key){ 50 return jedisCluster.get(key); 51 } 52 53 /** 54 * @author 劉其佳 55 * @description 56 * 一、在此處的參數key是一個可變參 57 * 二、在Redis中刪除的返回值是Integer類型 58 * 在Java中被轉成了Long類型 59 * @param * param *:key 60 * @date 2019/8/28 61 * @return java.lang.Long 62 * @throws 63 */ 64 public Long del(String key){ 65 return jedisCluster.del(key); 66 } 67 68 /** 69 * @author 劉其佳 70 * @description 71 * 經過key設置失效時間(單位是秒) 72 * @param * param *:key 73 * param *:seconds 74 * @date 2019/8/28 75 * @return java.lang.Long 76 * @throws 77 */ 78 public Long expire(String key,Integer seconds){ 79 return jedisCluster.expire(key,seconds); 80 } 81 }
查詢圖書信息的service:
1 /** 2 * @author 劉其佳 3 * @description 4 * 注意:service層不容許注入service 5 * 由於在applicationContext.xml中配置事務後,事務都是在service中的 6 * 當service注入另外一個事務時,會形成事務串線(若是線程是並行的沒有問題) 7 * applicationContext.xml配置文件中進行註解掃描: 8 * controller:必需要在springmvc中進行掃描 9 * service:必需要在spring中進行掃描 10 * 11 * @param * param *:null 12 * @date 2019/8/28 13 * @return 14 * @throws 15 */ 16 public Map<String ,Object> selectAllBooks(RedisService redisService){ 17 /** 18 * 一、從Redis中查詢全部圖書信息 19 * 二、判斷從Redis中是否查詢出數據 20 * 三、沒有查詢到的話再從數據庫中查詢數據 21 * 四、判斷是否從數據庫中查詢到數據 22 * 五、若查詢到數據,則返回結果,而且將數據放入Redis中 23 * 六、利用try...catch判斷是否存入Redis中 24 * 七、若沒有存入Redis中,要作出處理,此處爲再次往Redis中存放一次 25 */ 26 Map<String,Object> resultMap=new HashMap<String, Object>(); 27 //一、 28 String bookString=redisService.get(bookKey); 29 //二、 30 if(null==bookString||"".equals(bookString)){ 31 //三、 32 List<Book> bookList = bookMapper.selectAll(); 33 //四、 34 if(bookList.size()>0){ 35 //六、 36 try { 37 //五、 38 redisService.set(bookKey, JSONUtil.toJsonString(bookList)); 39 //將從數據庫中查詢到的數據存入resultMap中 40 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 41 resultMap.put(StatusEnum.SUCCESS.getResultName(),bookList); 42 } catch (Exception e) { 43 //七、 44 redisService.set(bookKey, JSONUtil.toJsonString(bookList)); 45 //若是仍是沒有將數據放入Redis中,此時咱們將resultMap裏面的code換成404,可是用戶仍是可以查詢到數據 46 resultMap.put(StatusEnum.FAILED.getCodeName(),StatusEnum.FAILED.getCode()); 47 resultMap.put(StatusEnum.FAILED.getResultName(),bookList); 48 e.printStackTrace(); 49 return resultMap; 50 } 51 }else { 52 //說明數據庫中沒有數據,放入500 53 resultMap.put(StatusEnum.ERROR.getCodeName(),StatusEnum.ERROR.getCode()); 54 } 55 }else { 56 //!!!可能會存在Redis中的數據與數據庫中的數據不一致的狀況 57 //因此此處從Redis中獲取到數據後與從數據庫中查詢到的數據作出一個判斷(長度是否一致) 58 List<Book> bookList = JSONUtil.toList(bookString, Book.class); 59 List<Book> bookList1 = bookMapper.selectAll(); 60 if(bookList.size()==bookList1.size()){ 61 //將查詢從Redis中查詢到的數據存入到resultMap 62 //由於是從Redis中查詢到的數據,此處給轉回正常狀態下的List集合類型的數據, 63 //正好從數據庫中查詢到的數據也是List,保持一致 64 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 65 resultMap.put(StatusEnum.SUCCESS.getResultName(),JSONUtil.toList(bookString,Book.class)); 66 }else { 67 //發現數據不一致時,丟棄Redis中的數據,並將數據庫中的數據存入Redis中 68 redisService.set(bookKey,JSONUtil.toJsonString(bookList)); 69 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 70 resultMap.put(StatusEnum.SUCCESS.getResultName(),bookList1); 71 } 72 73 } 74 return resultMap; 75 }
七、控制層的BookController:
1 /** 2 * @Author 劉其佳 3 * @DateTime 2019/8/28 22:25 4 * @Project_Name SpringBootRedis 5 */ 6 @RestController 7 public class BookController { 8 9 @Autowired 10 private BookService bookService; 11 @Autowired 12 private RedisService redisService; 13 14 @RequestMapping("/") 15 public List<Book> selectAllBooks(){ 16 Map<String, Object> resultMap = bookService.selectAllBooks(redisService); 17 List<Book> bookList=null; 18 //若是查詢到的resultMap中的code值爲200,說明直接從Redis中查到數據了 19 if(StatusEnum.SUCCESS.getCode().equals((resultMap.get(StatusEnum.SUCCESS.getCodeName())))){ 20 bookList= (List<Book>) resultMap.get(StatusEnum.SUCCESS.getResultName()); 21 System.out.println("everything is ok!"); 22 }else { 23 //若是code值不爲200,則有兩種狀況:404即Redis錯誤; 500即mysql錯誤 24 if(StatusEnum.ERROR.getCode().equals((resultMap.get(StatusEnum.SUCCESS.getCodeName())))){ 25 //一、result到最後也沒存進去值,數據庫都出現了問題 26 System.out.println("數據庫出現了問題"); 27 }else{ 28 //二、result有值,Redis出現了問題,但從數據庫查詢了數據 29 bookList= (List<Book>) resultMap.get(StatusEnum.SUCCESS.getResultName()); 30 System.out.println("redis出現了問題"); 31 } 32 } 33 return bookList; 34 }
在本次項目中爲了不魔法值問題,使用了枚舉:
新建一個包statuscode ,在其中新建類:StatusEnum
1 package com.aaa.liu.redis.statuscode; 2 3 /** 4 * @Author 劉其佳 5 * @DateTime 2019/8/29 20:08 6 * @Project_Name SpringBootRedis 7 */ 8 public enum StatusEnum { 9 SUCCESS(200,"操做失敗","code","result"), 10 FAILED(404,"操做成功","code","result"), 11 ERROR(500,"操做失敗","code","result"); 12 13 private Integer code; 14 private String msg; 15 private String codeName; 16 private String resultName; 17 18 /** 19 * 給枚舉賦值 20 * @param code 21 * @param msg 22 * @param codeName 23 */ 24 StatusEnum(Integer code, String msg, String codeName,String resultName) { 25 this.code = code; 26 this.msg = msg; 27 this.codeName = codeName; 28 this.resultName=resultName; 29 } 30 31 public Integer getCode() { 32 return code; 33 } 34 35 public void setCode(Integer code) { 36 this.code = code; 37 } 38 39 public String getMsg() { 40 return msg; 41 } 42 43 public void setMsg(String msg) { 44 this.msg = msg; 45 } 46 47 public String getCodeName() { 48 return codeName; 49 } 50 51 public void setCodeName(String codeName) { 52 this.codeName = codeName; 53 } 54 55 public String getResultName() { 56 return resultName; 57 } 58 59 public void setResultName(String resultName) { 60 this.resultName = resultName; 61 } 62 }
mapper中的BookMapper查詢全部圖書:
1 /** 2 * @Author 劉其佳 3 * @DateTime 2019/8/27 20:19 4 * @Project_Name springbootshiro 5 */ 6 @Mapper 7 public interface BookMapper { 8 //查詢全部書籍 9 @Select("select id,book_name bookName,book_price bookPrice from book ") 10 List<Book> selectAll(); 11 }
其中的注意事項已在代碼中寫出!
此處插入一個關於ajax的用法:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml" 3 xmlns:th="http://www.thymeleaf.org" 4 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Index</title> 8 <script type="text/javascript" src="/jquery-3.2.1.min.js"></script> 9 </head> 10 11 <script> 12 /** 13 * 1.頁面自動加載完成後,函數自動執行,查詢出數據並拼接處table表單,將數據放入其中 14 * */ 15 $(document).ready( selectBookTest()); 16 17 function selectBookTest(){ 18 $.ajax({ 19 type:"POST", 20 url:"selectBooks", 21 dataType:"JSON", 22 success:function (msg) { 23 var tr=""; 24 for(var i=0;i<msg.length;i++){ 25 tr+="<tr><td>"+msg[i].id+"</td>"; 26 tr+="<td>"+msg[i].bookName+"</td>"; 27 tr+="<td>"+msg[i].bookPrice+"</td></tr>"; 28 //不能直接使用下面的方式,不然會只有一行數據;要使用+= 29 // tr="<tr><td>"+msg[i].id+"</td><td>"+msg[i].bookName+"</td><td>"+msg[i].bookPrice+"</td></tr>" 30 } 31 tr+="<tr><td onclick='testBookAdd()'>添加</td></tr>"; 32 $("#test_Book").append("<table border='1px solid black'>" + 33 "<thead>" + 34 "<tr >" + 35 "<th>圖書編號</th>" + 36 "<th>圖書名稱</th>" + 37 "<th>圖書價格</th>" + 38 "</tr>" + 39 tr + 40 "</thead>" + 41 "</table>") 42 } 43 44 }) 45 } 46 47 /** 48 * 2.點擊添加按鈕,將查詢所得的數據清空,並拼接出新的form表單用以輸入要添加的數據 49 */ 50 function testBookAdd() { 51 $("#test_Book").empty(); 52 $("#test_Book").append("<form id='formBook'><table border='1px solid red'>" + 53 "<tr><td>圖書名稱:</td><td><input type='text' name='bookName'/></td></tr>" + 54 "<tr><td>圖書價格:</td><td><input type='text' name='bookPrice'/></td></tr>"+ 55 "<tr><td><input type='button' value='submit' onclick='bookAdd()'/></td></tr>"+ 56 "</table></form>") 57 } 58 59 /** 60 * 3.輸入完數據,使用ajax調用添加方法添加 61 */ 62 function bookAdd() { 63 $.ajax({ 64 type:"POST", 65 url:"testAddBook", 66 data:$("#formBook").serialize(), 67 dataType:"json", 68 success:function (msg) { 69 if(null==msg){ 70 alert("添加失敗") 71 }else{ 72 $("#test_Book").empty(); 73 selectBookTest(); 74 } 75 } 76 }) 77 } 78 79 </script> 80 <body> 81 82 <div id="test_Book"></div> 83 </body> 84 85 86 </html>