Ibatis/MyBatis select dynamically without need to create any Pojo / Mapper - ibatis

Is there any way to select/update/delete dynamically using Ibatis/MyBatis?
When I say "dynamically" it means I don't want to create any POJO/DataMapper at all.
Any URL example would be welcomed.

Yes, just set the resultType attribute to map and the table data will be placed into a HashMap of column names to values. If the query returns more than 1 row, the mapped rows will be put into a List. If you want to select a single column, you can get just that value (as String, int, etc) or as a list.
<select id="test1" resultType="map">select * from user</select>
<select id="test2" resultType="map" parameterType="int">
select * from user where id=#{value}</select>
<select id="test3" resultType="string">select name from user</select>
...
// returns a list of maps
List test = sqlSession.selectList("test1");
// returns a single map
Object map = sqlSession.selectOne("test2", 0);
// returns a list of strings
List names = sqlSession.selectList("test3");
This applies to MyBatis 3; I think you can do something similar in iBatis 2.

The following approach can be useful.
Say, you have some generic select interface, like:
public interface IAutoRepository {
/**
* The automatically generated insertPKs sql statements.
* Parts of the query can be set manually in the sql (insert-select query).
*
* #param items the {#link WhereStmt} statements
* #return the inserted rows count
*/
#Transactional
<T extends WhereStmt> Integer insertPKs(#Param("items") List<T> items);
/**
* Returns the value based on the {#link Parameter} class
*
* #param param the {#link Parameter} instance
* #return the searched value in a {#link java.util.Map} form
*/
#MapKey("VAL")
<T extends Parameter> Map<String, Map<String, ?>> getDistinctValues(#Param("param") T param);
}
According to some external type (say, single column or date range or range whatsoever) you can define the following query in the template Common.xml:
<sql id="includeDistinctValues">
SELECT
<choose>
<when test='param.type.name() == "set"'>
DISTINCT ${param.column} AS val
</when>
<when test='param.type.name() == "date" or param.type.name() == "range"'>
<some uid> AS val,
MIN(${param.minColumn}) AS min,
MAX(${param.maxColumn}) AS max
</when>
</choose>
FROM ${entityTable}
</sql>
What you receive from mybatis is the java.util.Map. Then you can you use it some kind like:
public enum StmtType {
set((valMap) -> {
final Set<String> distinctValues = valMap
.values()
.stream()
.map(val -> (String) val.get("VAL"))
//use in date/range case
//final Date minDate = (Date) val.get("MIN");
//final Date maxDate = (Date) val.get("MAX");
.collect(Collectors.toSet());
return distinctValues;
},
(values, params) -> {
final SetParameter parameter = (SetParameter) params.getParams();
return new WhereSetStmt<>(parameter.getColumn(), values, params.getIncludeEmptyValues());
});
#Getter
private Function<Map<String, Map<String, ?>>, ? extends Iterable> toValue;
#Getter
private BiFunction<Collection, DataParam, ? extends WhereStmt> toWhereStmt;
StmtType(
Function<Map<String, Map<String, ?>>, ? extends Iterable> toValue,
BiFunction<Collection, DataParam, ? extends WhereStmt> toWhereStmt
) {
this.toValue = toValue;
this.toWhereStmt = toWhereStmt;
}
}
where the SetParameter can be represented as the following:
#Getter
public class SetParameter extends Parameter {
/**
* Column in sql query,
*/
private final String column;
public SetParameter(String column) {
super(StmtType.set);
this.column = column;
}
}
Moreover, you can define some WhereStmt like:
public abstract class WhereStmt {
/**
* Type of the statement
*/
private final StmtType type;
/**
* Shall empty values be included.
*/
private final boolean includeEmptyValues;
}
#Getter
public class WhereSetStmt<T> extends WhereStmt {
/**
* The column for `column` IN (...) statement
*/
private String column;
/**
* Values for `column` IN (...) statement
*/
private Collection<T> values;
public WhereSetStmt(String column, Collection<T> values, boolean includeEmptyValues) {
super(StmtType.set, includeEmptyValues);
this.column = column;
this.values = values;
}
}
#Getter
#AllArgsConstructor
public final class DataParam<P extends Parameter> {
/**
* Whether to include nullable values.
*/
private final Boolean includeEmptyValues;
/**
* Represents database required information for later processing and sql statements generation.
*/
private final P params;
}
Finally, in mybatis generic Common.xml you can use it like:
<sql id="includeInsertPkSelect">
SELECT DISTINCT(${id})
FROM ${entityTable}
</sql>
<sql id="includeInsertPkWhere">
<if test="items != null and items.size() > 0">
AND
<foreach collection="items" item="item" index="i" separator="AND">
<choose>
<when test='item.type.name() == "set" and ( item.values != null and item.values.size() > 0 or item.includeEmptyValues )'>
(
<if test="item.values != null and item.values.size() > 0">
${item.column} IN
<foreach item="value" collection="item.values" separator="," open="("
close=")">
#{value}
</foreach>
<if test="item.includeEmptyValues">
OR
</if>
</if>
<if test="item.includeEmptyValues">
${item.column} IS null
</if>
)
</when>
<when test='item.type.name() == "date"'>
(
COALESCE(${item.column}, SYSDATE + 1000000)
BETWEEN #{item.from} AND #{item.to}
<if test="item.includeEmptyValues">
OR ${item.column} IS NULL
</if>
)
</when>
<when test='item.type.name() == "range"'>
(
COALESCE(${item.column}, 1000000000)
BETWEEN #{item.from} AND #{item.to}
<if test="item.includeEmptyValues">
OR ${item.column} IS NULL
</if>
)
</when>
</choose>
</foreach>
</if>
</sql>
And combine sql statements as the template, say:
<insert id='insertPKs'>
INSERT INTO requiredTable
<include refid="Common.includeInsertPkSelect">
<property name="id" value="entityId"/>
<property name="entityTable" value="entityTable"/>
</include>
<include refid="Common.includeInsertPkWhere">
<property name="id" value="entityId"/>
<property name="entityTable" value="entityTable"/>
</include>
</insert>

Yes, it should be possible to build the mapping in runtime through an API, and to use Maps instead of entity classes.

Yes, you can do select/update/delete without using POJO.
In myBatis you can write your query in Mapper XML and set the resultType that will be returned from SQL statement and store that in objects.
For Example,
<mapper namespace = "data">
<select id = "fetch" resultType = "java.util.HashMap">
select * from table_name
</select>
</mapper>
You can use List of Maps to store those results.
List<Map<String,Object>> results = session.selectList("data.fetch");

Related

SpringBoot : RestAPI display the unique record

Table :
this is table name
Requirement :
Using RestAPI call populate unique cateID, categoryName , but I am getting the whole record.
Table
Table Data which I am using
Code Description :
Repository :
#Repository
public interface CategoryRepository extends JpaRepository<xCategory,Integer>
{
// #Query("SELECT DISTINCT a.catID,a.categoryName FROM ccCategory a order by categoryName asc")
#Query("SELECT DISTINCT a.catID, a.categoryName FROM xCategory a order by categoryName asc")
List<ccCategory> getCategoryName();
}
Rest Controller:
#RestController
#CrossOrigin(origins = "http://localhost:4200", maxAge = 3600)
public class HomeResource {
private final Logger log = LoggerFactory.getLogger(HomeResource.class);
#Autowired
CategoryRepository categoryRepository;
#GetMapping("/getAllCategory")
public List<ccCategory> getAllCategory() {
// public List<String> getAllCategory() {
System.out.println("***** Call : API getAllCategory() ******");
List<ccCategory> cCategory = categoryRepository.findAll();
return cCategory;
}
Angular Code :
<label class="control-label">Category: </label>
<select [(ngModel)]="listAllCategory" name="xxcategory" class="form-control" required>
<option *ngFor="let xxcategory of listAllCategory" [value]="xxcategory.catID">
{{xxcategory.categoryName}}
</option>
</select>
Problem :
Drop Down populating all the table value but I want only the UNIQUE value like only one time catID , categoryName.
You have to add a variable to keep the selected element in your select and then change your [(ngModel)]="listAllCategory" with [(ngModel)]="selectedCategory"

How can I output ManyToOne data with doctrine (Symfony 4)?

This is my entity "data":
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\DataRepository")
*/
class Data
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=10, unique=true)
*/
private $uuid;
/**
* #ORM\Column(type="string", length=255)
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="Fields")
* #ORM\JoinColumn(name="field", referencedColumnName="id")
*/
private $fields;
public function getId(): ?int
{
return $this->id;
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = $content;
}
public function getUuid(): ?string
{
return $this->uuid;
}
public function setUuid(string $uuid): self
{
$this->uuid = $uuid;
return $this;
}
public function getFields(): ?Fields
{
return $this->fields;
}
public function setFields(?Fields $fields): self
{
$this->fields = $fields;
return $this;
}
}
I am getting the data via doctrine:
$output = $this->em->getRepository(Data::class)->findAll();
The output:
array:2 [▼
0 => Data {#7060 ▼
-id: 1
-uuid: "12345"
-content: "blabla"
-fields: Fields {#7164 ▼
+__isInitialized__: false
-id: 6
-name: null
-uuid: null
-productgroup: null
-type: null
…2
}
}
1 => Data {#7165 ▶}
]
The problem is, that the data of the ManyToOne "fields" captures only the id. But not the name or productgroup. It is all "null". But in my database it is not null.
This usually happen when you are dumping objects with relations. For checking that fields relation has not null values. Do this:
dump($output[0]->getFields()->getName())
This is because relations from doctrine are not loaded in this case.
If you access the relation before, e.g.
$output->fields
The relation will actually be loaded and the fields won't be null if you dump it afterwards
See Relationships and Proxy Classes
This is cause by doctrine lazy loading, in order to avoid overloading memmory.
As #AythaNzt says, if you loop trougth entities you will be able to acces their properties (when you as for getField() doctrine trigger a query to fetch their data)
Check this to display all child fields: Avoid lazy loading Doctrine Symfony2

Displaying array values in laravel 5.2

I have the following hasMany() relationship in App\User.php ,
public function partner_preference_occupation()
{
return $this-hasMany('App\Models\User\PartnerPreferenceOccupation', 'user_id');
}
The following is my PartnerPreferenceOccupation Model,
<?php
namespace App\Models\User;
use App\Models\BaseModel,
App\Models\ValidationTrait;
class PartnerPreferenceOccupation extends BaseModel {
use ValidationTrait;
public function __construct() {
parent::__construct();
$this->__validationConstruct();
}
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'partner_preferences_occupation';
protected $fillable = array('user_id', 'occupation_id');
protected $dates = array();
public $uploadPath = array();
protected function setRules() {
$this->val_rules = array();
}
protected function setAttributes() {
$this->val_attributes = array();
}
public function occupation_name() {
return $this->belongsTo('App\Models\Master\OccupationModel', 'occupation_id');
}
}
I want to display the array of occupation name in my view. I tried the following code, but it fails.
{{$obj->partner_preference_occupation ? $obj-partner_preference_occupation->occupation_name->name : null}}
The error is as follows,
Undefined property:Illuminate\Database\Eloquent\Collection::$occupation_name
How can I display them.Thanks in advance.
There is a typo in $obj-partner_preference_occupation->occupation_name->name
Should be $obj->partner_preference_occupation->occupation_name->name
Make sure that occupation_name exists in the collection. You can check by doing the following in your View:
dump( $obj->partner_preference_occupation );
It might help you identify where the issue lies.
Also, to display a collection as an array, you can use the toArray() method, e.g.:
$obj->partner_preference_occupation->toArray()
Ok I got it,
As it is an array of values to be fetched, I used foreach to display them in my view as,
#if($occ=$obj->partner_preference_occupation->lists('occupation_name'))
#foreach($occ as $oc)
{{$oc->name}}
#endforeach
#endif
occupatoin_name is the name of my relationship that I used in my model. And that worked for me :)

Doctrine searching on Entity without any filters

I was wondering if there is a way to search in entity without applying any filters. For Example I would like to build a textfiled in my template where a ajax post method is calling to a controller with purpose searching the whole entity.
My code:
$user = $this->getDoctrine()
->getRepository('AppBundle:QCE_SUBD')
->find('%'.$SearchParam.'%')
->getQuery();
$DSUB = $user->getArrayResult();
dump($DSUB);
I;m not sure how the function should be written, so if some one is willing to help it will be highly appreciate :)
You should just create a function that return a JsonResponse with an array of your result.
// In your controller
/**
* #Route("/ajax_action")
*/
public function ajaxAction(Request $request)
{
// Get the posted parameter from your ajax call
$searchParam = $request->get('searchParam');
// Request your entity
$user = $this->getDoctrine()
->getRepository('AppBundle:QCE_SUBD')
->createQueryBuilder('q')
->where('q.username LIKE :searchParam')
->orWhere('q.otherColumn LIKE :searchParam')
->setParameter('searchParam', '%'.$searchParam.'%')
->getQuery();
// Check if it's an ajax call
if ($request->isXMLHttpRequest()) {
return new JsonResponse($user->getArrayResult();
}
// Return an error
throw new \Exception('Wrong call!');
}
For the search part you need to implement a full text search, here is a tutorial on how to implement it :
http://ourcodeworld.com/articles/read/90/how-to-implement-fulltext-search-mysql-with-doctrine-and-symfony-3
P.S : You should be sure of what you need in your query. If you want it to be scalable, you should take a look at better search engine method as ElasticSearch or Solr.
You can inspire yourself from the following function. It iterates dynamically through all fields of the entity and depending on the type of the field a condition is applied to the query builder:
/**
* Creates the query builder used to get the results of the search query
* performed by the user in the "search" view with a given "keyword".
*
* #param array $entityConfig
* #param string $searchQuery
* #param string|null $sortField
* #param string|null $sortDirection
* #param string|null $dqlFilter
*
* #return DoctrineQueryBuilder
*/
public function createSearchQueryBuilder(array $entityConfig, $searchQuery, $sortField = null, $sortDirection = null, $dqlFilter = null)
{
/* #var EntityManager */
$em = $this->doctrine->getManagerForClass($entityConfig['class']);
/* #var DoctrineQueryBuilder */
$queryBuilder = $em->createQueryBuilder()
->select('entity')
->from($entityConfig['class'], 'entity')
;
$queryParameters = array();
foreach ($entityConfig['search']['fields'] as $name => $metadata) {
$isNumericField = in_array($metadata['dataType'], array('integer', 'number', 'smallint', 'bigint', 'decimal', 'float'));
$isTextField = in_array($metadata['dataType'], array('string', 'text', 'guid'));
if ($isNumericField && is_numeric($searchQuery)) {
$queryBuilder->orWhere(sprintf('entity.%s = :exact_query', $name));
// adding '0' turns the string into a numeric value
$queryParameters['exact_query'] = 0 + $searchQuery;
} elseif ($isTextField) {
$searchQuery = strtolower($searchQuery);
$queryBuilder->orWhere(sprintf('LOWER(entity.%s) LIKE :fuzzy_query', $name));
$queryParameters['fuzzy_query'] = '%'.$searchQuery.'%';
$queryBuilder->orWhere(sprintf('LOWER(entity.%s) IN (:words_query)', $name));
$queryParameters['words_query'] = explode(' ', $searchQuery);
}
}
if (0 !== count($queryParameters)) {
$queryBuilder->setParameters($queryParameters);
}
if (!empty($dqlFilter)) {
$queryBuilder->andWhere($dqlFilter);
}
if (null !== $sortField) {
$queryBuilder->orderBy('entity.'.$sortField, $sortDirection ?: 'DESC');
}
return $queryBuilder;
}
The source code comes from the EasyAdminBundle.

symfony render json_array entity type and save using form

Imagine I have an Article entity.
And in this entity have a report attribute which is an json_array type.
Json_array's data may like
{"key1":"value1","key2":{"k1":"v1","k2","v2"...},"key3":["v1","v2","v3"...]...}.
I mean the json_array may contains simple key:value or the value may also contains key:vaule or the value may an array.
Now I don't know how to use symfony form to render and save these json_array like other normal attribute(e.g.,title).At the same time,I want to manage the key label name with an meaning name just like change the title field's label.
How to achieve this,I feel very difficult.
class Article
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var array
*
* #ORM\Column(name="report", type="json_array")
*/
private $report;
}
Maybe you can use json_decode to pass from json to array and later in the form you can use:
->add('someField', null, array('mapped' => false))
And in the success do something with this values
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// some awesome code here
}
Hope this can help you.
Roger
you can create a data type to manage your report field :
namespace Acme\TestBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ReportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('key1',TextType::class,array('label' => 'K1'))
->add('key2',TextType::class,array('label' => 'K2'))
;
}
public function getName()
{
return 'report';
}
}
Then declare the new data type :
# src/Acme/TestBundle/Resources/config/services.yml
services:
acme_test.form.type.report:
class: Acme\TestBundle\Form\Type\ReportType
tags:
- { name: form.type, alias: report }
And finally use this new dataType in your form :
->add('reports',
'collection',
array(
'type'=>'report',
'prototype'=>true,
'allow_add'=>true,
'allow_delete'=>true,
'options'=>array(
)
)
)

Resources