緩存與分布式鎖
即時性、數據一致要求不高的
訪問量大且更新頻率不高的數據
(讀多,寫少)
常用緩存中間件 redis
Spring
如果用spring的情況下,由于redis沒有受spring的管理,
則我們需要自己先寫一個redis的配置類,寫好方法把redistemplate 作為bean return出來受 spring管理,
(很老的方法了,但現在企業也在用)或者 直接寫xml進行注冊,然后放入web.xml中
<context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/classes/applicationContext.xml,/WEB-INF/classes/config/spring/xjtec_helps/spring_xjtec_helps_taskhelp_starts.xml,/WEB-INF/classes/spring-session.xml</param-value></context-param>
Springboot 或cloud
引入maven redis的starter,然后spring把基礎工作都給你寫好了,你只需要引入,按照規則前綴,在配置中寫好配置值,想用的時候 @autowired進來用就完事了
配置類
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure.data.redis;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;@Configuration(proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
//所有的配置信息通過RedisProperties.class得到,這里引入過來
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"})@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}
}//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure.data.redis;import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(
//配置文件中,按照這個前綴寫就可以了prefix = "spring.redis"
)
public class RedisProperties {private int database = 0;private String url;private String host = "localhost";private String password;private int port = 6379;private boolean ssl;private Duration timeout;private String clientName;private Sentinel sentinel;private Cluster cluster;private final Jedis jedis = new Jedis();private final Lettuce lettuce = new Lettuce();public static class Pool {private int maxIdle = 8;private int minIdle = 0;private int maxActive = 8;private Duration maxWait = Duration.ofMillis(-1L);private Duration timeBetweenEvictionRuns;public Pool() {}public int getMaxIdle() {return this.maxIdle;}public void setMaxIdle(int maxIdle) {this.maxIdle = maxIdle;}public int getMinIdle() {return this.minIdle;}public void setMinIdle(int minIdle) {this.minIdle = minIdle;}public int getMaxActive() {return this.maxActive;}public void setMaxActive(int maxActive) {this.maxActive = maxActive;}public Duration getMaxWait() {return this.maxWait;}public void setMaxWait(Duration maxWait) {this.maxWait = maxWait;}public Duration getTimeBetweenEvictionRuns() {return this.timeBetweenEvictionRuns;}public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) {this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;}}
}
則會自動引入
三級目錄的展示,通過緩存一點一點優化
最基本的緩存的加入
緩存中有則直接用,無則進入數據庫查詢并存放
@Overridepublic Map<String, List<Catalog2VO>> getCatalogJson() {//給緩存中放json字符串,拿出的json字符串,還用逆轉為能用的對象類型 【序列化與反序列化】//1.加入緩存邏輯String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(StringUtils.isEmpty(catalogJSON)){//2.緩存中沒有,查詢數據庫Map<String, List<Catalog2VO>> catalogJsonFromDb = getCatalogJsonFromDb();//3.查到的數據再放入緩存,將對象轉為json放在緩存中String s = JSON.toJSONString(catalogJsonFromDb);redisTemplate.opsForValue().set("catalogJSON",s);}//轉為我們指定的對象Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON,new TypeReference<Map<String,List<Catalog2VO>>>(){});return result;}
為了解決擊穿的問題,優化代碼
緩存中所遇到的三種問題
這塊代碼只能在單項目(this)中能控制到1人進到數據庫查找值,分布式時則控制不住(分了幾臺服務器就有幾個人能進),但也比所有有人進入到數據庫查找好
鎖中的代碼,一定要由三部分,
1.查緩存,又則返回
2.查數據庫
3.再入緩存
//首先嘗試緩存拿值@Overridepublic Map<String, List<Catalog2VO>> getCatalogJson() {//給緩存中放json字符串,拿出的json字符串,還用逆轉為能用的對象類型 【序列化與反序列化】//1.加入緩存邏輯String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(StringUtils.isEmpty(catalogJSON)){//2.緩存中沒有,查詢數據庫Map<String, List<Catalog2VO>> catalogJsonFromDb = getCatalogJsonFromDb();return catalogJsonFromDb;}//轉為我們指定的對象Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON,new TypeReference<Map<String,List<Catalog2VO>>>(){});return result;}//從數據庫查詢并封裝分類數據public Map<String, List<Catalog2VO>> getCatalogJsonFromDb() {//1.synchronized(this): SpringBoot所有的組件在容器中都是單列的,則此時this可以進行鎖住,但分布式時無法鎖住synchronized (this){//得到鎖以后,我們應該再去緩存中查找一次,以防有人已經查到了String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(!StringUtils.isEmpty(catalogJSON)){//進鎖后,有可能別人已經提前進來并且找到了,則直接緩存拿到返回即可Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});return result;}//1.將數據庫的多次查詢變為一次List<CategoryEntity> selectList = baseMapper.selectList(null);List<CategoryEntity> level1Categorys = getParent_cid(selectList,0L);Map<String,List<Catalog2VO>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k->k.getCatId().toString(),v->{List<CategoryEntity> categoryEntities = getParent_cid(selectList,v.getCatId());List<Catalog2VO> catalog2VOS = null;if(categoryEntities!=null){catalog2VOS = categoryEntities.stream().map(l2->{Catalog2VO catalog2VO = new Catalog2VO(v.getCatId().toString(),null,l2.getCatId().toString(),l2.getName());//1、找當前二級分類的三級分類封裝成voList<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());if(level3Catelog!=null){List<Catalog2VO.Catalog3Vo> collect = level3Catelog.stream().map(l3->{Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catalog3Vo;}).collect(Collectors.toList());catalog2VO.setCatalog3List(collect);}return catalog2VO;}).collect(Collectors.toList());}return catalog2VOS;}));//3.查到的數據再放入緩存,將對象轉為json放在緩存中String s = JSON.toJSONString(parent_cid);redisTemplate.opsForValue().set("catalogJSON",s,1, TimeUnit.DAYS);return parent_cid;}}
本地嘗試分布式
賦值多臺服務器但注意端口分開
然后nginx訪問過來時,由網關幫我們賦值均衡
根據主機名進行斷言攔截到請求,然后轉到uri
穿透
訪問的數據,數據庫不存在,則也無法放入緩存中,則不斷訪問數據庫
設置null值返回
雪崩
key大批量同時過期
設置過期時間時加入隨機值
擊穿
熱點key,過期后,同時又大量的值進行訪問到數據庫了
給熱點key加鎖,不能讓所有人都去數據庫進行查詢
分布式鎖
小提示
docker中引進的redis,通過命令 docker exec -it redis redis-cli
進入到redis的操作界面
同過redis的setnx進行一次簡易的分布式鎖
public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");//操作完成后,釋放鎖return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}
簡易鎖的缺點
解鎖之前,出異常了,則會導致死鎖
補救:加鎖時一定要帶上過期時間
eg. set lock 111 EX 300 NX(redis命令)
加鎖和過期時間的設置要進行原子操作
public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");//操作完成后,釋放鎖return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}
刪鎖時也要進行原子操作,并且要避免不要刪除掉別人的加的鎖,所有鎖的值要是唯一的,刪除要進行原子操作時,運行lua腳本
public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {String UUID = java.util.UUID.randomUUID().toString();//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",UUID,300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class),Arrays.asList("lock"), UUID);return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}
最終版本
public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {String UUID = java.util.UUID.randomUUID().toString();//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",UUID,300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作Map<String, List<Catalog2VO>> dataFromDb = new HashMap<>();if(lock){try{//加鎖成功..執行業務dataFromDb = getDataFromDb();}finally {String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class),Arrays.asList("lock"), UUID);}return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}
分布式鎖框架 redisson
引用springboot的redissonstarter則基本不用自己再寫代碼,只需配置文件寫好響應的屬性即可
首次使用則我們自己進行配置一下,則只單獨引入redisson的配置
寫好配置類(放入到ioc容器中,后面則可直接使用)
單個redis的情況,集群則寫入多個ip
@Configuration()
public class MyRedissonConfig {@Bean(destroyMethod="shutdown")public RedissonClient redisson() throws IOException{//創建配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.29.103:6379");//根據conf創建使用redisson所必須要的 RedissonClient示例RedissonClient redissonClient = Redisson.create(config);return redissonClient;}
}
redisson的鎖都是可重入鎖
可重入鎖:
可重入鎖是指一個線程可以重復獲取它已經持有的鎖。當一個線程已經持有某個鎖時,它可以再次獲取該鎖而不會被阻塞,這種特性稱為"可重入性"(Reentrancy)。重入計數:每次獲取鎖計數器加1,釋放時減1,計數器為0時鎖才真正釋放
避免死鎖:允許同一線程多次獲取同一把鎖
公平性選擇:ReentrantLock可以配置為公平鎖或非公平鎖
中斷響應:ReentrantLock支持在等待鎖時響應中斷
redisson的api用法與lock的api用法相同
redisson的基礎應用
@GetMapping("/hello")public String hello(){//1.獲取一把鎖,只要鎖的名字一樣,就是同一把鎖RLock lock = redisson.getLock("my-lock");//2.加鎖lock.lock();//阻塞式等待,默認加的鎖30s,只要這邊把鎖放了,其他人可以獲得到//看門狗,業務時間長時,自動給鎖續期//業務完成,(獲取不到線程id)則不再續期,不手動解鎖的話,30s后自動解鎖//2.1 可以自己指定過期時間,但指定時間后,不會觸發開門狗,則不會自動續期,我們指定了時間后,就會去執行 固定的腳本//lock.lock(10,TimeUnit.SECONDS);//2.2lock.tryLock(100,10,TimeUnit.SECONDS); 可以設定線程等待鎖的時間,超過則放棄//實際應用時,則還是需要指定時間,不需要續期,只要指定的時間長一點即可(超過業務時間)try {System.out.println("加鎖成功,執行業務..."+Thread.currentThread().getId());Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {lock.unlock();}}
讀寫鎖(也是可重入鎖,當前線程可對鎖多次操作)
//通過讀寫鎖,能保證讀的數據一定是最新的, 寫鎖是一個排他鎖,讀鎖是一個共享鎖//寫鎖沒釋放時,其他人既不能寫也不能讀,沒有寫鎖時,則讀鎖就像不存在一樣
//正在讀的過程中,寫鎖也一定要等待讀鎖釋放@GetMapping("/write")@ResponseBodypublic String writeValue(){//讀寫鎖RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";RLock rLock = lock.writeLock();try{//1、操作數據庫 則加寫鎖 , 讀數據加讀鎖rLock.lock();s = UUID.randomUUID().toString();Thread.sleep(3000);stringRedisTemplate.opsForValue().set("writeValue",s);} catch (InterruptedException e) {e.printStackTrace();}finally {rLock.unlock();}return s;}@GetMapping("/read")@ResponseBodypublic String readValue(){RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";RLock rLock = lock.readLock();rLock.lock();try{s = stringRedisTemplate.opsForValue().get("writeValue");} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return s;}
信號量(主要用于限流操作,可以設置線程上線)
@GetMapping("/park")@ResponseBodypublic String park() throws InterruptedException{RSemaphore park = redisson.getSemaphore("park");//park.acquire();//直接獲取一個信號量,如果此時信號量沒有了,則會卡住//則通常用下面的方法,得到boolean返回值boolean b = park.tryAcquire();if(b){//執行業務}else{//返回提示return "fail";}return "ok=>"+b;}@GetMapping("/go")@ResponseBodypublic String go() throws InterruptedException{RSemaphore park = redisson.getSemaphore("park");park.release();return "ok";}
閉鎖
/*** 等待5個班級都沒人后,才能鎖大門*/@GetMapping("/lockDoor")@ResponseBodypublic String lockDoor() throws InterruptedException{RCountDownLatch door = redisson.getCountDownLatch("door");door.trySetCount(5);//設置好鎖需要達到的數量door.await();//等待閉鎖的完成//到達數量后則可以放行return "放假了...";}@GetMapping("/gogogo/{id}")public String gogogo(@PathVariable("id") Long id){RCountDownLatch door = redisson.getCountDownLatch("door");door.countDown();//觸發一次,則減少一個return id+"班的人都走了";}
緩存一致性
/**(根據自己的業務仔細判斷)* 緩存里面的數據如何和數據庫保持一致* 緩存數據一致性* 1)、雙寫模式 操作數據庫的時候 ,重新緩存保存* 2)、失效模式 操作數據庫的時候,直接刪除相應緩存* 但這兩個操作如果不加鎖的話,還是會導致臟數據* 加鎖后性能很低,要加也要加讀寫鎖* 總結:* 我們能放入緩存的數據本就不應該是實時性,一致性要求超高的數據,所以一般設定好過期時間都是能正常使用的* 我們不應該過度設計,增加系統的復雜性* 遇到頻繁修改、實時要求高,那就不要緩存、不加鎖、自己查數據庫就好了* 阿里有一個cannal* @return*/
SpringCache
不同的CacheManager 都對緩存進行了進一次的分區,例如對于redis的CacheManager,雖然緩存都是存在redis得同一個庫,但到這邊還是進行了分區處理
整合SpringCache簡化緩存開發
- 引入依賴
spring-boot-starter-cache
spring-boot-starter-data-redis //我們用redis進行的緩存
2)寫配置
(1)自動配置了那些
CacheAutoConfiguration會導入 RedisCacheConfiguration;
自動配置好了緩存管理器RedisCacheManager
(2)配置使用了redis作為緩存
spring.cache.type = redis
3)測試使用緩存
@Cacheable 觸發將數據保存到緩存的操作
@CacheEvict 觸發將數據從緩存刪除的操作
@CachePut 不影響方法的執行進行緩存的更新
@Cacheing 組合以上多個操作
@CacheConfig 在類別共享緩存的相同配置
3)測試使用緩存
主類加注解 啟動緩存 @EnbaleCaching
然后進行相應的注解進行相應的緩存操作
// 每一個需要緩存的數據我們都來指定要放到那個名字的緩存【緩存的分區(按照業務類型分)】@Cacheable({"category"})//代表當前方法的結果需要緩存,如果緩存中有,則不調用此方法@Overridepublic List<CategoryEntity> getLevel1Categorys() {System.out.println("調用了getLevel1Categorys...");// 查詢父id=0return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));}
自定義調整緩存配置
/*** 啟動Cacheable注釋能進行緩存的使用 是有默認值的* key自動生產名字* 緩存的值默認使用jdk序列化* 默認ttl時間 -1* * 則這三樣內容我們肯定要進行自定義的* 注解中定義了 key值 通過表達式或者字符串的形式定義key值* 緩存時間則需要在配置文件中進行修改* 為了更好的遷移性 在緩存信息保存為json形式,調整格式時,就需要寫類調整配置類了* @return*/// 每一個需要緩存的數據我們都來指定要放到那個名字的緩存【緩存的分區(按照業務類型分)】@Cacheable(value = {"category"},key = "'level1Categorys'")//代表當前方法的結果需要緩存,如果緩存中有,則不調用此方法@Overridepublic List<CategoryEntity> getLevel1Categorys() {System.out.println("調用了getLevel1Categorys...");// 查詢父id=0return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));}
自定義配置類
package com.atguigu.gulimall.product.config;import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** 通過注解 @EnableConfigurationProperties 可以綁定配置類* 但為什么我們要通過注解的形式拿到該類(CacheProperties)和該類的值呢,因為CacheProperties并不是受spring容器管理,則通過這種形式導入*/
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//把啟動注解放在我們的自己自定義緩存配置上面
@EnableCaching
//自定義緩存配置
public class MyCacheConfig {//在類CacheProperties的源碼我們可以看到/*** @ConfigurationProperties(prefix = "spring.cache") 是通過這個前綴和自己的屬性進行值匹配的** 然后為了讓這個類生效 我們就加了頂部的 @EnableConfigurationProperties(CacheProperties.class)*///@Autowired 可以通過屬性的方式得到//CacheProperties cache//這里是該redis的相關配置,要該其他的,導入其他的配置類在按這要寫就可以了@Bean //放在參數上面 也是可以直接導入值的RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){//拿到最原始的實例,然后進行修改覆蓋即可RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();//設置key的序列化方式config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));//主要就是把默認jdk序列化的形式 轉為jsonconfig = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));CacheProperties.Redis redis = cacheProperties.getRedis();//將配置文件中的所以配置都生效if(redis.getTimeToLive()!=null){config = config.entryTtl(redis.getTimeToLive());}if(redis.getKeyPrefix()!=null){config = config.prefixKeysWith(redis.getKeyPrefix());}if(!redis.isCacheNullValues()){config = config.disableCachingNullValues();}if(!redis.isUseKeyPrefix()){config = config.disableKeyPrefix();}return config;}}
再看下我們的配置文件中進行了那些配置
spring.cache.type=redis
spring.cache.redis.time-to-live=360000
#指定我們的緩存前綴,如果沒有就用緩存的名字作為前綴
spring.cache.redis.key-prefix=CACHE_
#但需要手動開啟
spring.cache.redis.use-key-prefix=false
#防止緩存擊穿 是否緩存空值
spring.cache.redis.cache-null-values=true
數據更新時,緩存注釋的操作
/*** 級聯更新所有關聯的數據* @param category*/ //注意key為固定字符串時一定要加單引號,不然會被當作動態的表達式//刪除單個指定key緩存 @CacheEvict(value = "category", key = "'getLevel1Categorys'")//寫的方法上加入該注解@CacheEvict 進行的失效模式,刪除之前的緩存//刪除多個指定key緩存 則要用到注解 @Caching 可以進行組合操作@Caching(evict = {@CacheEvict(value = "category", key = "'getLevel1Categorys'"),@CacheEvict(value = "category", key = "'getCatalogJson'")})//直接進行分區刪除緩存 更為方便@CacheEvict(value = "category",allEntries = true)//如果數據返回值時 用注解CachePut 則會把數據寫入緩存中@CachePut@Transactional@Overridepublic void updateCascade(CategoryEntity category) {this.updateById(category);categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());}