29 Aralık 2020 Salı

SpringTest MockMvcResultMatchers Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
content metodu
Örnek ver
cookie metodu
Örnek ver
flash metodu
Örnek ver
forwardedUrl metodu
Örnek ver
forwardedUrlPattern metodu
Örnek ver
forwardedUrlTemplate metodu
Örnek ver
handler metodu
Örnek ver
header metodu
Örnek ver

jsonPath metodu
Cevap olarak gelen json nesnesinin bir alanına erişmek için kullanılır
Örnek
Şöyle yaparız
@Test
public void should_parse_id_from_plain_string() throws Exception {
  //given
  final String emptyId = "00000000-0000-0000-0000-000000000000";

  //when
  mockMvc.perform(MockMvcRequestBuilders.get("/find-id/" + emptyId))

  //then
  .andExpect(status().isOk())
  .andExpect(jsonPath("$.id", equalTo(emptyId)));
}

model metodu
Örnek ver
redirectedUrl metodu
Örnek ver
redirectedUrlPattern metodu
Örnek ver
redirectedUrlTemplate metodu
Örnek ver
request metodu
Örnek ver
status metodu
Örnek ver
view metodu
Örnek ver
xpath metodu
Örnek ver

SpringMVC Java Server Pages - JSP

Giriş
JSP artık ölü bir teknoloji. Yeni geliştirme yapılmıyor.
Maven
Örnek
Şu satırı dahil ederiz
<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
  <scope>provided</scope>
</dependency>
Örnek
JST de kullanacaksak şöyle yaparız
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.6.4</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
</dependency>

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
  <scope>provided</scope>
</dependency>
applicaton.properties
JSP sayfalarının nerede olduğunu belirtiriz
Örnek
Şöyle yaparız. Bu durumda jsp sayfaları "src/main/webapp/jsp" dizinindedir
spring.mvc.view.prefix=/jsp/
spring.mvc.view.suffix=.jsp
Şöyle yaparız
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Testing JSP</title>
</head>
<body>
<h1>Hello World !</h1>
</body>
</html>
Controller'dan jsp'ye yönlendirmek için şöyle yaparız
@Controller
public class HomeController {
  @RequestMapping("/")
  public String goToHome() {
    return "index";
  }
}
Örnek
Şöyle yaparız
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
src/main/webapp/WEB-INF/jsp/ dizininde şöyle yaparız
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello ${name}!</title>
</head>
<body>
  <h2 class="hello-title">Hello ${name}!</h2>
</body>
</html>

SpringData MongoDB @Query Anotasyonu

Giriş
Şu satırı dahil ederiz. Bu anotasyon ile Derived Query olarak gerçekleştirilemeyen işler yapabiliriz.
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
Bazı güzel örnekler burada.

Alana Göre Arama
Örnek
Şöyle yaparız. Aslında bu örnek çok iyi değil çünkü findByTitle zaten Derived Query olarak ta yapılabilirdi.
public interface MovieRepository extends MongoRepository<Movie,String> {
  @Query("{'title':?0}")
  Optional<Movie> findByTitle(String title);
}

SpringData @Transactional - Rollback

Giriş
1. Rollback işlemi için unchecked bir exception fırlatmak yeterli. 
2. Eğer checked exception kullanmak istersek rollbackFor alanını kullanmak gerekir. Açıklaması şöyle
... by default in spring transactions are rolled back only for runtime exceptions. When a checked exception is thrown from your code and you don’t explicitly tell spring that it should rollback the transaction then it get’s committed.
3. Eğer bazı unchecked exception'lar için rollback olmasın istiyorsak noRollbackFor alanı kullanılır.

4. rollbackFor ve noRollbackFor alanları parametre olarak class alır. Bunların string yani class ismi alan türevleri ise rollbackForClassName ve noRollbackForClassName alanları. 

1. rollbackFor Alanı - Class Parametre
Örnek
Şu kod java.lang.ArithmeticException fırlattığı için otomatik rollback yapar
@Transactional
public Response<Void> updateMedium(UserVO userVO){

  UserDomain userDomain = this.getIfPresent(userVO.getId());
  userDomain.setDeleted(userVO.getDeleted());
  UserDomain saved = userRepository.save(userDomain);
  int a = 2 / 0;

  return Response.success();
}
Şu kod artık exception fırlatıyor. unchecked exception fırlatmadığı için artık otomatik rollback yapmaz.
@Transactional
public Response<Void> updateMedium(UserVO userVO) throws Exception {

  UserDomain userDomain = this.getIfPresent(userVO.getId());
  userDomain.setDeleted(userVO.getDeleted());
  userDomain.setPassword(userVO.getPassword());
  userDomain.setUsername(userVO.getUsername());
  userDomain.setCreateTime(new Date());
  UserDomain saved = userRepository.save(userDomain);
  try {
    int a = 2 / 0;
  } catch (Exception e) {
    throw new Exception();
  }

  return Response.success();
}
Kodu şöyle yapmak gerekir
@Transactional(rollbackFor = Exception.class)
Örnek - unchecked exception
Şu kod rollback yapar, çünkü unchecked exception fırlatıyor
@Transactional
public void rollbacksOnRuntimeException() {
  jdbcTemplate.execute("insert into test_table values('rollbacksOnRuntimeException')");
  throw new RuntimeException("Rollback!");
}
Örnek - checked exception
Şu kod rollback yapmaz çünkü checked exception fırlatıyor ancak rollbackOn alanı tanımlı değil
@Transactional
public void noRollbackOnCheckedException() throws Exception {
  jdbcTemplate.execute("insert into test_table values('noRollbackOnCheckedException')");
  throw new Exception("Simple exception");
}
Örnek
Şu iki kod da düzgün rollback yapar. Birincisi checked exception fırlatıyor ve rollBackOn alanı tanımlı. İkincisi ise rollbackOn tanımlı olsa bile zaten unchecked exception fırlatabilir.
@Transactional(rollbackFor = CustomCheckedException.class)
public void withRollbackOnAndDeclaredException() throws CustomCheckedException {
  jdbcTemplate.execute("insert into test_table
values('withRollbackForAndDeclaredException')"
);
  throw new CustomCheckedException("rollback me");
}

@Transactional(rollbackFor = CustomCheckedException.class)
public void withRollbackOnAndRuntimeException() throws CustomCheckedException {
  jdbcTemplate.execute("insert into test_table
values('withRollbackOnAndRuntimeException')"
);
  throw new RuntimeException("rollback me");
}
2. noRollbackFor Alanı - Class Parametre
Aynı transaction içinde bir yerde exception fırlatılıyorsa normalde transaction rollback edilir. Ancak bu exception'ı biz bilerek göz ardı etmek istersek iki tane temel çözüm var.

1. noRollbackFor kullanmak
2. Exception fırlatan kodu REQUIRES_NEW ile yeni bir transaction içine almak.

Örnek
Elimizde şöyle bir kod olsun.
@Transactional
public void addPeople(String name) {
  personRepository.saveAndFlush(new Person("Jack", "Brown"));
  personRepository.saveAndFlush(new Person("Julia", "Green"));
  String resultName = name;
  try {
    personValidateService.validateName(name);
  }
  catch (IllegalArgumentException e) {
    log.error("name is not allowed. Using default one");
    resultName = "DefaultName";
  }
  personRepository.saveAndFlush(new Person(resultName, "Purple"));
  }
}
validateName() şöyle olsun
@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}
Burada amaç eğer validateName() exception fırlatırsa bile default name ile bir kayıt yaratmak. Ancak bu kayıt yaratılmıyor. Çünkü tüm Spring kodları bir @Transaction ile işaretli olduğu için aynı transaction içinde çalışıyor. Spring her hangi bir yerde exception yakarlarsa geri kalan işlemleri de yapmaz. Bu durumda şöyle yaparız
@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional(noRollbackFor = IllegalArgumentException.class)
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}
Açıklaması şöyle
The default @Transactional propagation is REQUIRED. It means that the new transaction is created if it’s missing. And if it’s present already, the current one is supported. So, the whole request is being executed within a single transaction.

Anyway, there is a caveat. If the RuntimeException throws out of the transactional proxy, Spring marks the current transaction as rollback only. That’s exactly what happened in our case. PersonValidateService.validateName throws IllegalArgumentException. Transactional proxy tracks it and sets on the rollback flag. Later executions during the transaction have no effect because they ought to be rolled back in the end.
Örnek
Şöyle yaparız
@Service
@Transactional(
  isolation = Isolation.READ_COMMITTED, 
  propagation = Propagation.SUPPORTS, 
  readOnly = false, 
  timeout = 30)
public class CarService {
 
  @Autowired
  private CarRepository carRepository;
 
  @Transactional(
    rollbackFor = IllegalArgumentException.class, 
    noRollbackFor = EntityExistsException.class,
    rollbackForClassName = "IllegalArgumentException", 
    noRollbackForClassName = "EntityExistsException")
  public Car save(Car car) {
    return carRepository.save(car);
  }
}

28 Aralık 2020 Pazartesi

SpringData Jdbc JdbcTemplate.update metodu - Insert, Delete, Update İçindir

Giriş
İsmi update olmasına rağmen Insert, Delete, Update için kullanılabilir. 

update metodu  - sql + args
İmzası şöyle. Sonuç olarak kaç tane satırın etkilendiğini döner.
public int update(String sql) throws DataAccessException
public int update(String sql, Object... args) throws DataAccessException public int update(String
sql, Object[] args, int [] argTypes) throws DataAccessException
1. args parametreleri teker teker veya Object[] şeklinde geçilebilir.
2. Sonuç olarak kaç tane satırın etkilendiğini belirten bir int döner

Örnek
Şöyle yaparız
String INSERT_USER_QUERY = "INSERT INTO USER(id,first_name,last_name,gender,age)
VALUES(?,?,?,?,?)"
;
String UPDATE_USER_QUERY = "UPDATE user SET first_name=?,last_name=?,gender=?,age=?
WHERE id=?"
;
String DELETE_USER_QUERY = "DELETE FROM user WHERE id=?";

@Override
public int save(User user) {
  return jdbcTemplate.update(INSERT_USER_QUERY, new Object[] { user.getId(),
user.getFirstName(),
    user.getLastName(), user.getGender(), user.getAge() });
}

@Override
public int update(User user) {
  return jdbcTemplate.update(UPDATE_USER_QUERY, new Object[] { user.getFirstName(),
user.getLastName(),
    user.getGender(), user.getAge(), user.getId() });
}

@Override
public int delete(int id) {
  return jdbcTemplate.update(DELETE_USER_QUERY, new Object[] { id });
}
Örnek
Parametre kullanmak için şöyle yaparız.
jdbctemplate.update(sql, arg1, arg2);
Örnek
Dizi kullanmak için şöyle yaparız
jdbctemplate.update(sql, new Object[]{arg1, arg2});
Kaç satırın etkilendiği sonucunu almak için şöyle yaparız.
String insertQuery = "<db query for insert>";
Object[] args = new Object[]{Integer.valueOf(...), ...,...,...,null,null};
int result = jdbcTemplate.update(insertQuery, args);
Örnek - insert
Oracle'da sequence kullanmak için şöyle yaparız.
String sql = "insert into Employee values (id_seq.nextval, ?, ?, ?)";
jdbcTemplate.update(sql, name, age, salary);
Örnek - insert
Şöyle yaparız.
jdbcTemplate.update("INSERT INTO Test (ID, NAME) VALUES (?, ?)",
  new Object[]{101, "Dave"}
);
update metodu  - sql + PreparedStatementCreator
Örnek
Şöyle yaparız.
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
  new PreparedStatementCreator() {
    public PreparedStatement createPreparedStatement(Connection connection)
      throws SQLException {
      PreparedStatement ps =
        connection.prepareStatement(INSERT_SQL, new String[] {"id"});
        ps.setString(1, name);
        return ps;
      }
    },
keyHolder);
Örnek
PreparedStatementCreator yerine lambda kullanılabilir. Şöyle yaparız
jdbcTemplate.update(connection -> {
  PreparedStatement ps = connection.prepareStatement(
"insert into my (id , file, file_name ) values ( ? , ? , ? )"
    new String[]{"id"});
  ps.setInt(1, id);
  ps.setString(2, base64EncodedFile);  
  ps.setString(3, fileName);
  return ps;
});